agent_ui: Add `@diagnostics` mention to the thread (#42270)

Mikhail Pertsev and Bennet Bo Fenner created

Closes #31351

# Diagnostics Mention in New Threads

## Overview

Adds the `@diagnostics` mention to the new Agent Panel threads so users
can inject current LSP diagnostics (errors by default) without switching
to a text thread. The diagnostics mention is fully integrated into ACP
mention parsing, the context picker, and the message-editor pipeline so
it round-trips cleanly and shows up in the standard `@` menu.

## Context

- **Request:** bring `/diagnostics` parity to the β€œNew Thread” assistant
experience.
- **Scope:** diagnostics only; `/terminal` mention would be implemented
in a separate PR.
- **Docs:** updated Agent Panel docs + changelog.

## Implementation Details

1. **Mention plumbing**
- Added `MentionUri::Diagnostics` to `acp_thread`, including parsing
(`zed:///agent/diagnostics?include_warnings=true`) and icon/name
metadata.
- Tests ensure diagnostics links round-trip via Markdown mention
serialization.

2. **Context picker / completion**
- New `ContextPickerMode::Diagnostics` exposes an `@diagnostics` entry
in the mention menu.
- Completions turn `@diagnostics` into a fully fledged mention, reusing
the existing confirmation pipeline.

3. **Message editor + thread serialization**
- Resolving the mention calls the existing diagnostics collector from
`assistant_slash_commands`, embedding the tool output inline with other
context blocks (`<diagnostics>…</diagnostics>`).
- Thread-link handling ignores diagnostics backlinks so clicking them
doesn’t try to reopen nonexistent resources.
   

# How it looks
<img width="800" height="480" alt="image"
src="https://cf5gpe8lxo.ufs.sh/f/EmJ5Xl877qJO1mzC9Zrn8AmJZHeShC4RoUwvTMlF2tfPzj06"
/>

Release Notes:

- Allow mentioning diagnostics in the agent panel via `@diagnostics`

---------

Co-authored-by: Bennet Bo Fenner <bennet@zed.dev>

Change summary

crates/acp_thread/src/mention.rs                           |  75 ++
crates/agent/src/thread.rs                                 |  12 
crates/agent_ui/src/acp/message_editor.rs                  |   6 
crates/agent_ui/src/acp/thread_view.rs                     |   1 
crates/agent_ui/src/completion_provider.rs                 | 247 +++++++
crates/agent_ui/src/mention_set.rs                         |  37 +
crates/assistant_slash_commands/src/diagnostics_command.rs |  43 +
docs/src/ai/agent-panel.md                                 |   2 
8 files changed, 401 insertions(+), 22 deletions(-)

Detailed changes

crates/acp_thread/src/mention.rs πŸ”—

@@ -40,6 +40,12 @@ pub enum MentionUri {
         id: PromptId,
         name: String,
     },
+    Diagnostics {
+        #[serde(default = "default_include_errors")]
+        include_errors: bool,
+        #[serde(default)]
+        include_warnings: bool,
+    },
     Selection {
         #[serde(default, skip_serializing_if = "Option::is_none")]
         abs_path: Option<PathBuf>,
@@ -135,6 +141,20 @@ impl MentionUri {
                         id: rule_id.into(),
                         name,
                     })
+                } else if path == "/agent/diagnostics" {
+                    let mut include_errors = default_include_errors();
+                    let mut include_warnings = false;
+                    for (key, value) in url.query_pairs() {
+                        match key.as_ref() {
+                            "include_warnings" => include_warnings = value == "true",
+                            "include_errors" => include_errors = value == "true",
+                            _ => bail!("invalid query parameter"),
+                        }
+                    }
+                    Ok(Self::Diagnostics {
+                        include_errors,
+                        include_warnings,
+                    })
                 } else if path.starts_with("/agent/pasted-image") {
                     Ok(Self::PastedImage)
                 } else if path.starts_with("/agent/untitled-buffer") {
@@ -200,6 +220,7 @@ impl MentionUri {
             MentionUri::Thread { name, .. } => name.clone(),
             MentionUri::TextThread { name, .. } => name.clone(),
             MentionUri::Rule { name, .. } => name.clone(),
+            MentionUri::Diagnostics { .. } => "Diagnostics".to_string(),
             MentionUri::Selection {
                 abs_path: path,
                 line_range,
@@ -221,6 +242,7 @@ impl MentionUri {
             MentionUri::Thread { .. } => IconName::Thread.path().into(),
             MentionUri::TextThread { .. } => IconName::Thread.path().into(),
             MentionUri::Rule { .. } => IconName::Reader.path().into(),
+            MentionUri::Diagnostics { .. } => IconName::Warning.path().into(),
             MentionUri::Selection { .. } => IconName::Reader.path().into(),
             MentionUri::Fetch { .. } => IconName::ToolWeb.path().into(),
         }
@@ -299,6 +321,21 @@ impl MentionUri {
                 url.query_pairs_mut().append_pair("name", name);
                 url
             }
+            MentionUri::Diagnostics {
+                include_errors,
+                include_warnings,
+            } => {
+                let mut url = Url::parse("zed:///").unwrap();
+                url.set_path("/agent/diagnostics");
+                if *include_warnings {
+                    url.query_pairs_mut()
+                        .append_pair("include_warnings", "true");
+                }
+                if !include_errors {
+                    url.query_pairs_mut().append_pair("include_errors", "false");
+                }
+                url
+            }
             MentionUri::Fetch { url } => url.clone(),
         }
     }
@@ -312,6 +349,10 @@ impl fmt::Display for MentionLink<'_> {
     }
 }
 
+fn default_include_errors() -> bool {
+    true
+}
+
 fn single_query_param(url: &Url, name: &'static str) -> Result<Option<String>> {
     let pairs = url.query_pairs().collect::<Vec<_>>();
     match pairs.as_slice() {
@@ -504,6 +545,40 @@ mod tests {
         assert_eq!(parsed.to_uri().to_string(), https_uri);
     }
 
+    #[test]
+    fn test_parse_diagnostics_uri() {
+        let uri = "zed:///agent/diagnostics?include_warnings=true";
+        let parsed = MentionUri::parse(uri, PathStyle::local()).unwrap();
+        match &parsed {
+            MentionUri::Diagnostics {
+                include_errors,
+                include_warnings,
+            } => {
+                assert!(include_errors);
+                assert!(include_warnings);
+            }
+            _ => panic!("Expected Diagnostics variant"),
+        }
+        assert_eq!(parsed.to_uri().to_string(), uri);
+    }
+
+    #[test]
+    fn test_parse_diagnostics_uri_warnings_only() {
+        let uri = "zed:///agent/diagnostics?include_warnings=true&include_errors=false";
+        let parsed = MentionUri::parse(uri, PathStyle::local()).unwrap();
+        match &parsed {
+            MentionUri::Diagnostics {
+                include_errors,
+                include_warnings,
+            } => {
+                assert!(!include_errors);
+                assert!(include_warnings);
+            }
+            _ => panic!("Expected Diagnostics variant"),
+        }
+        assert_eq!(parsed.to_uri().to_string(), uri);
+    }
+
     #[test]
     fn test_invalid_scheme() {
         assert!(MentionUri::parse("ftp://example.com", PathStyle::local()).is_err());

crates/agent/src/thread.rs πŸ”—

@@ -223,6 +223,7 @@ impl UserMessage {
         const OPEN_FETCH_TAG: &str = "<fetched_urls>";
         const OPEN_RULES_TAG: &str =
             "<rules>\nThe user has specified the following rules that should be applied:\n";
+        const OPEN_DIAGNOSTICS_TAG: &str = "<diagnostics>";
 
         let mut file_context = OPEN_FILES_TAG.to_string();
         let mut directory_context = OPEN_DIRECTORIES_TAG.to_string();
@@ -231,6 +232,7 @@ impl UserMessage {
         let mut thread_context = OPEN_THREADS_TAG.to_string();
         let mut fetch_context = OPEN_FETCH_TAG.to_string();
         let mut rules_context = OPEN_RULES_TAG.to_string();
+        let mut diagnostics_context = OPEN_DIAGNOSTICS_TAG.to_string();
 
         for chunk in &self.content {
             let chunk = match chunk {
@@ -312,6 +314,9 @@ impl UserMessage {
                         MentionUri::Fetch { url } => {
                             write!(&mut fetch_context, "\nFetch: {}\n\n{}", url, content).ok();
                         }
+                        MentionUri::Diagnostics { .. } => {
+                            write!(&mut diagnostics_context, "\n{}\n", content).ok();
+                        }
                     }
 
                     language_model::MessageContent::Text(uri.as_link().to_string())
@@ -372,6 +377,13 @@ impl UserMessage {
                 .push(language_model::MessageContent::Text(rules_context));
         }
 
+        if diagnostics_context.len() > OPEN_DIAGNOSTICS_TAG.len() {
+            diagnostics_context.push_str("</diagnostics>\n");
+            message
+                .content
+                .push(language_model::MessageContent::Text(diagnostics_context));
+        }
+
         if message.content.len() > len_before_context {
             message.content.insert(
                 len_before_context,

crates/agent_ui/src/acp/message_editor.rs πŸ”—

@@ -74,7 +74,11 @@ impl PromptCompletionProviderDelegate for Entity<MessageEditor> {
             if self.read(cx).thread_store.is_some() {
                 supported.push(PromptContextType::Thread);
             }
-            supported.extend(&[PromptContextType::Fetch, PromptContextType::Rules]);
+            supported.extend(&[
+                PromptContextType::Diagnostics,
+                PromptContextType::Fetch,
+                PromptContextType::Rules,
+            ]);
         }
         supported
     }

crates/agent_ui/src/completion_provider.rs πŸ”—

@@ -17,7 +17,7 @@ use lsp::CompletionContext;
 use ordered_float::OrderedFloat;
 use project::lsp_store::{CompletionDocumentation, SymbolLocation};
 use project::{
-    Completion, CompletionDisplayOptions, CompletionIntent, CompletionResponse,
+    Completion, CompletionDisplayOptions, CompletionIntent, CompletionResponse, DiagnosticSummary,
     PathMatchCandidateSet, Project, ProjectPath, Symbol, WorktreeId,
 };
 use prompt_store::{PromptStore, UserPromptId};
@@ -55,6 +55,7 @@ pub(crate) enum PromptContextType {
     Fetch,
     Thread,
     Rules,
+    Diagnostics,
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -92,6 +93,7 @@ impl TryFrom<&str> for PromptContextType {
             "fetch" => Ok(Self::Fetch),
             "thread" => Ok(Self::Thread),
             "rule" => Ok(Self::Rules),
+            "diagnostics" => Ok(Self::Diagnostics),
             _ => Err(format!("Invalid context picker mode: {}", value)),
         }
     }
@@ -105,6 +107,7 @@ impl PromptContextType {
             Self::Fetch => "fetch",
             Self::Thread => "thread",
             Self::Rules => "rule",
+            Self::Diagnostics => "diagnostics",
         }
     }
 
@@ -115,6 +118,7 @@ impl PromptContextType {
             Self::Fetch => "Fetch",
             Self::Thread => "Threads",
             Self::Rules => "Rules",
+            Self::Diagnostics => "Diagnostics",
         }
     }
 
@@ -125,6 +129,7 @@ impl PromptContextType {
             Self::Fetch => IconName::ToolWeb,
             Self::Thread => IconName::Thread,
             Self::Rules => IconName::Reader,
+            Self::Diagnostics => IconName::Warning,
         }
     }
 }
@@ -583,6 +588,103 @@ impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {
         })
     }
 
+    fn completion_for_diagnostics(
+        source_range: Range<Anchor>,
+        source: Arc<T>,
+        editor: WeakEntity<Editor>,
+        mention_set: WeakEntity<MentionSet>,
+        workspace: Entity<Workspace>,
+        cx: &mut App,
+    ) -> Vec<Completion> {
+        let summary = workspace
+            .read(cx)
+            .project()
+            .read(cx)
+            .diagnostic_summary(false, cx);
+        if summary.error_count == 0 && summary.warning_count == 0 {
+            return Vec::new();
+        }
+        let icon_path = MentionUri::Diagnostics {
+            include_errors: true,
+            include_warnings: false,
+        }
+        .icon_path(cx);
+
+        let mut completions = Vec::new();
+
+        let cases = [
+            (summary.error_count > 0, true, false),
+            (summary.warning_count > 0, false, true),
+            (
+                summary.error_count > 0 && summary.warning_count > 0,
+                true,
+                true,
+            ),
+        ];
+
+        for (condition, include_errors, include_warnings) in cases {
+            if condition {
+                completions.push(Self::build_diagnostics_completion(
+                    diagnostics_submenu_label(summary, include_errors, include_warnings),
+                    source_range.clone(),
+                    source.clone(),
+                    editor.clone(),
+                    mention_set.clone(),
+                    workspace.clone(),
+                    icon_path.clone(),
+                    include_errors,
+                    include_warnings,
+                    summary,
+                ));
+            }
+        }
+
+        completions
+    }
+
+    fn build_diagnostics_completion(
+        menu_label: String,
+        source_range: Range<Anchor>,
+        source: Arc<T>,
+        editor: WeakEntity<Editor>,
+        mention_set: WeakEntity<MentionSet>,
+        workspace: Entity<Workspace>,
+        icon_path: SharedString,
+        include_errors: bool,
+        include_warnings: bool,
+        summary: DiagnosticSummary,
+    ) -> Completion {
+        let uri = MentionUri::Diagnostics {
+            include_errors,
+            include_warnings,
+        };
+        let crease_text = diagnostics_crease_label(summary, include_errors, include_warnings);
+        let display_text = format!("@{}", crease_text);
+        let new_text = format!("[{}]({}) ", display_text, uri.to_uri());
+        let new_text_len = new_text.len();
+        Completion {
+            replace_range: source_range.clone(),
+            new_text,
+            label: CodeLabel::plain(menu_label, None),
+            documentation: None,
+            source: project::CompletionSource::Custom,
+            icon_path: Some(icon_path),
+            match_start: None,
+            snippet_deduplication_key: None,
+            insert_text_mode: None,
+            confirm: Some(confirm_completion_callback(
+                crease_text,
+                source_range.start,
+                new_text_len - 1,
+                uri,
+                source,
+                editor,
+                mention_set,
+                workspace,
+            )),
+        }
+    }
+
     fn search_slash_commands(&self, query: String, cx: &mut App) -> Task<Vec<AvailableCommand>> {
         let commands = self.source.available_commands(cx);
         if commands.is_empty() {
@@ -684,6 +786,8 @@ impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {
                 }
             }
 
+            Some(PromptContextType::Diagnostics) => Task::ready(Vec::new()),
+
             None if query.is_empty() => {
                 let recent_task = self.recent_context_picker_entries(&workspace, cx);
                 let entries = self
@@ -879,6 +983,20 @@ impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {
             entries.push(PromptContextEntry::Mode(PromptContextType::Fetch));
         }
 
+        if self
+            .source
+            .supports_context(PromptContextType::Diagnostics, cx)
+        {
+            let summary = workspace
+                .read(cx)
+                .project()
+                .read(cx)
+                .diagnostic_summary(false, cx);
+            if summary.error_count > 0 || summary.warning_count > 0 {
+                entries.push(PromptContextEntry::Mode(PromptContextType::Diagnostics));
+            }
+        }
+
         entries
     }
 }
@@ -982,6 +1100,28 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
                 })
             }
             PromptCompletion::Mention(MentionCompletion { mode, argument, .. }) => {
+                if let Some(PromptContextType::Diagnostics) = mode {
+                    if argument.is_some() {
+                        return Task::ready(Ok(Vec::new()));
+                    }
+
+                    let completions = Self::completion_for_diagnostics(
+                        source_range.clone(),
+                        source.clone(),
+                        editor.clone(),
+                        mention_set.clone(),
+                        workspace.clone(),
+                        cx,
+                    );
+                    if !completions.is_empty() {
+                        return Task::ready(Ok(vec![CompletionResponse {
+                            completions,
+                            display_options: CompletionDisplayOptions::default(),
+                            is_incomplete: false,
+                        }]));
+                    }
+                }
+
                 let query = argument.unwrap_or_default();
                 let search_task =
                     self.search_mentions(mode, query, Arc::<AtomicBool>::default(), cx);
@@ -1051,7 +1191,6 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
                                         cx,
                                     )
                                 }
-
                                 Match::Symbol(SymbolMatch { symbol, .. }) => {
                                     Self::completion_for_symbol(
                                         symbol,
@@ -1064,7 +1203,6 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
                                         cx,
                                     )
                                 }
-
                                 Match::Thread(thread) => Some(Self::completion_for_thread(
                                     thread,
                                     source_range.clone(),
@@ -1075,7 +1213,6 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
                                     workspace.clone(),
                                     cx,
                                 )),
-
                                 Match::RecentThread(thread) => Some(Self::completion_for_thread(
                                     thread,
                                     source_range.clone(),
@@ -1086,7 +1223,6 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
                                     workspace.clone(),
                                     cx,
                                 )),
-
                                 Match::Rules(user_rules) => Some(Self::completion_for_rules(
                                     user_rules,
                                     source_range.clone(),
@@ -1096,7 +1232,6 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
                                     workspace.clone(),
                                     cx,
                                 )),
-
                                 Match::Fetch(url) => Self::completion_for_fetch(
                                     source_range.clone(),
                                     url,
@@ -1106,7 +1241,6 @@ impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletio
                                     workspace.clone(),
                                     cx,
                                 ),
-
                                 Match::Entry(EntryMatch { entry, .. }) => {
                                     Self::completion_for_entry(
                                         entry,
@@ -1380,6 +1514,87 @@ impl MentionCompletion {
     }
 }
 
+fn diagnostics_label(
+    summary: DiagnosticSummary,
+    include_errors: bool,
+    include_warnings: bool,
+) -> String {
+    let mut parts = Vec::new();
+
+    if include_errors && summary.error_count > 0 {
+        parts.push(format!(
+            "{} {}",
+            summary.error_count,
+            pluralize("error", summary.error_count)
+        ));
+    }
+
+    if include_warnings && summary.warning_count > 0 {
+        parts.push(format!(
+            "{} {}",
+            summary.warning_count,
+            pluralize("warning", summary.warning_count)
+        ));
+    }
+
+    if parts.is_empty() {
+        return "Diagnostics".into();
+    }
+
+    let body = if parts.len() == 2 {
+        format!("{} and {}", parts[0], parts[1])
+    } else {
+        parts
+            .pop()
+            .expect("at least one part present after non-empty check")
+    };
+
+    format!("Diagnostics: {body}")
+}
+
+fn diagnostics_submenu_label(
+    summary: DiagnosticSummary,
+    include_errors: bool,
+    include_warnings: bool,
+) -> String {
+    match (include_errors, include_warnings) {
+        (true, true) => format!(
+            "{} {} & {} {}",
+            summary.error_count,
+            pluralize("error", summary.error_count),
+            summary.warning_count,
+            pluralize("warning", summary.warning_count)
+        ),
+        (true, _) => format!(
+            "{} {}",
+            summary.error_count,
+            pluralize("error", summary.error_count)
+        ),
+        (_, true) => format!(
+            "{} {}",
+            summary.warning_count,
+            pluralize("warning", summary.warning_count)
+        ),
+        _ => "Diagnostics".into(),
+    }
+}
+
+fn diagnostics_crease_label(
+    summary: DiagnosticSummary,
+    include_errors: bool,
+    include_warnings: bool,
+) -> SharedString {
+    diagnostics_label(summary, include_errors, include_warnings).into()
+}
+
+fn pluralize(noun: &str, count: usize) -> String {
+    if count == 1 {
+        noun.to_string()
+    } else {
+        format!("{noun}s")
+    }
+}
+
 pub(crate) fn search_files(
     query: String,
     cancellation_flag: Arc<AtomicBool>,
@@ -1833,6 +2048,11 @@ mod tests {
     #[test]
     fn test_mention_completion_parse() {
         let supported_modes = vec![PromptContextType::File, PromptContextType::Symbol];
+        let supported_modes_with_diagnostics = vec![
+            PromptContextType::File,
+            PromptContextType::Symbol,
+            PromptContextType::Diagnostics,
+        ];
 
         assert_eq!(
             MentionCompletion::try_parse("Lorem Ipsum", 0, &supported_modes),
@@ -1945,6 +2165,19 @@ mod tests {
             })
         );
 
+        assert_eq!(
+            MentionCompletion::try_parse(
+                "Lorem @diagnostics",
+                0,
+                &supported_modes_with_diagnostics
+            ),
+            Some(MentionCompletion {
+                source_range: 6..18,
+                mode: Some(PromptContextType::Diagnostics),
+                argument: None,
+            })
+        );
+
         // Disallowed non-file mentions
         assert_eq!(
             MentionCompletion::try_parse("Lorem @symbol main", 0, &[PromptContextType::File]),

crates/agent_ui/src/mention_set.rs πŸ”—

@@ -3,7 +3,7 @@ use agent::{ThreadStore, outline};
 use agent_client_protocol as acp;
 use agent_servers::{AgentServer, AgentServerDelegate};
 use anyhow::{Context as _, Result, anyhow};
-use assistant_slash_commands::codeblock_fence_for_path;
+use assistant_slash_commands::{codeblock_fence_for_path, collect_diagnostics_output};
 use collections::{HashMap, HashSet};
 use editor::{
     Anchor, Editor, EditorSnapshot, ExcerptId, FoldPlaceholder, ToOffset,
@@ -235,6 +235,10 @@ impl MentionSet {
                 ..
             } => self.confirm_mention_for_symbol(abs_path, line_range, cx),
             MentionUri::Rule { id, .. } => self.confirm_mention_for_rule(id, cx),
+            MentionUri::Diagnostics {
+                include_errors,
+                include_warnings,
+            } => self.confirm_mention_for_diagnostics(include_errors, include_warnings, cx),
             MentionUri::PastedImage => {
                 debug_panic!("pasted image URI should not be included in completions");
                 Task::ready(Err(anyhow!(
@@ -510,6 +514,37 @@ impl MentionSet {
             })
         })
     }
+
+    fn confirm_mention_for_diagnostics(
+        &self,
+        include_errors: bool,
+        include_warnings: bool,
+        cx: &mut Context<Self>,
+    ) -> Task<Result<Mention>> {
+        let Some(project) = self.project.upgrade() else {
+            return Task::ready(Err(anyhow!("project not found")));
+        };
+
+        let diagnostics_task = collect_diagnostics_output(
+            project,
+            assistant_slash_commands::Options {
+                include_errors,
+                include_warnings,
+                path_matcher: None,
+            },
+            cx,
+        );
+        cx.spawn(async move |_, _| {
+            let output = diagnostics_task.await?;
+            let content = output
+                .map(|output| output.text)
+                .unwrap_or_else(|| "No diagnostics found.".into());
+            Ok(Mention::Text {
+                content,
+                tracked_buffers: Vec::new(),
+            })
+        })
+    }
 }
 
 #[cfg(test)]

crates/assistant_slash_commands/src/diagnostics_command.rs πŸ”—

@@ -188,7 +188,7 @@ impl SlashCommand for DiagnosticsSlashCommand {
         let path_style = project.read(cx).path_style(cx);
         let options = Options::parse(arguments, path_style);
 
-        let task = collect_diagnostics(project.clone(), options, cx);
+        let task = collect_diagnostics_output(project.clone(), options, cx);
 
         window.spawn(cx, async move |_| {
             task.await?
@@ -198,10 +198,10 @@ impl SlashCommand for DiagnosticsSlashCommand {
     }
 }
 
-#[derive(Default)]
-struct Options {
-    include_warnings: bool,
-    path_matcher: Option<PathMatcher>,
+pub struct Options {
+    pub include_errors: bool,
+    pub include_warnings: bool,
+    pub path_matcher: Option<PathMatcher>,
 }
 
 const INCLUDE_WARNINGS_ARGUMENT: &str = "--include-warnings";
@@ -218,6 +218,7 @@ impl Options {
             }
         }
         Self {
+            include_errors: true,
             include_warnings,
             path_matcher,
         }
@@ -228,7 +229,7 @@ impl Options {
     }
 }
 
-fn collect_diagnostics(
+pub fn collect_diagnostics_output(
     project: Entity<Project>,
     options: Options,
     cx: &mut App,
@@ -282,11 +283,17 @@ fn collect_diagnostics(
                 continue;
             }
 
-            project_summary.error_count += summary.error_count;
+            let has_errors = options.include_errors && summary.error_count > 0;
+            let has_warnings = options.include_warnings && summary.warning_count > 0;
+            if !has_errors && !has_warnings {
+                continue;
+            }
+
+            if options.include_errors {
+                project_summary.error_count += summary.error_count;
+            }
             if options.include_warnings {
                 project_summary.warning_count += summary.warning_count;
-            } else if summary.error_count == 0 {
-                continue;
             }
 
             let last_end = output.text.len();
@@ -301,7 +308,12 @@ fn collect_diagnostics(
                 .log_err()
             {
                 let snapshot = cx.read_entity(&buffer, |buffer, _| buffer.snapshot());
-                collect_buffer_diagnostics(&mut output, &snapshot, options.include_warnings);
+                collect_buffer_diagnostics(
+                    &mut output,
+                    &snapshot,
+                    options.include_warnings,
+                    options.include_errors,
+                );
             }
 
             if !glob_is_exact_file_match {
@@ -358,10 +370,11 @@ pub fn collect_buffer_diagnostics(
     output: &mut SlashCommandOutput,
     snapshot: &BufferSnapshot,
     include_warnings: bool,
+    include_errors: bool,
 ) {
     for (_, group) in snapshot.diagnostic_groups(None) {
         let entry = &group.entries[group.primary_ix];
-        collect_diagnostic(output, entry, snapshot, include_warnings)
+        collect_diagnostic(output, entry, snapshot, include_warnings, include_errors)
     }
 }
 
@@ -370,6 +383,7 @@ fn collect_diagnostic(
     entry: &DiagnosticEntryRef<'_, Anchor>,
     snapshot: &BufferSnapshot,
     include_warnings: bool,
+    include_errors: bool,
 ) {
     const EXCERPT_EXPANSION_SIZE: u32 = 2;
     const MAX_MESSAGE_LENGTH: usize = 2000;
@@ -381,7 +395,12 @@ fn collect_diagnostic(
             }
             ("warning", IconName::Warning)
         }
-        DiagnosticSeverity::ERROR => ("error", IconName::XCircle),
+        DiagnosticSeverity::ERROR => {
+            if !include_errors {
+                return;
+            }
+            ("error", IconName::XCircle)
+        }
         _ => return,
     };
     let prev_len = output.text.len();

docs/src/ai/agent-panel.md πŸ”—

@@ -85,7 +85,7 @@ You can turn this off, though, through the `agent.single_file_review` setting.
 Although Zed's agent is very efficient at reading through your code base to autonomously pick up relevant context, manually adding whatever would be useful to fulfill your prompt is still encouraged as a way to not only improve the AI's response quality but also to speed up its response time.
 
 In Zed's Agent Panel, all pieces of context are added as mentions in the panel's message editor.
-You can type `@` to mention files, directories, symbols, previous threads, and rules files.
+You can type `@` to mention files, directories, symbols, previous threads, rules files, and diagnostics.
 
 Copying images and pasting them in the panel's message editor is also supported.