acp: Fix `MessageEditor::set_message` for sent messages (#36715)

Agus Zubiaga created

The `PromptCapabilities` introduced in previous PRs were only getting
set on the main message editor and not for the editors in user messages.
This caused a bug where mentions would disappear after resending the
message, and for the completion provider to be limited to files.

Release Notes:

- N/A

Change summary

crates/agent_ui/src/acp/entry_view_state.rs |  9 ++++-
crates/agent_ui/src/acp/message_editor.rs   | 34 ++++++++++++----------
crates/agent_ui/src/acp/thread_view.rs      | 16 ++++++----
3 files changed, 35 insertions(+), 24 deletions(-)

Detailed changes

crates/agent_ui/src/acp/entry_view_state.rs 🔗

@@ -1,7 +1,7 @@
-use std::ops::Range;
+use std::{cell::Cell, ops::Range, rc::Rc};
 
 use acp_thread::{AcpThread, AgentThreadEntry};
-use agent_client_protocol::ToolCallId;
+use agent_client_protocol::{PromptCapabilities, ToolCallId};
 use agent2::HistoryStore;
 use collections::HashMap;
 use editor::{Editor, EditorMode, MinimapVisibility};
@@ -27,6 +27,7 @@ pub struct EntryViewState {
     prompt_store: Option<Entity<PromptStore>>,
     entries: Vec<Entry>,
     prevent_slash_commands: bool,
+    prompt_capabilities: Rc<Cell<PromptCapabilities>>,
 }
 
 impl EntryViewState {
@@ -35,6 +36,7 @@ impl EntryViewState {
         project: Entity<Project>,
         history_store: Entity<HistoryStore>,
         prompt_store: Option<Entity<PromptStore>>,
+        prompt_capabilities: Rc<Cell<PromptCapabilities>>,
         prevent_slash_commands: bool,
     ) -> Self {
         Self {
@@ -44,6 +46,7 @@ impl EntryViewState {
             prompt_store,
             entries: Vec::new(),
             prevent_slash_commands,
+            prompt_capabilities,
         }
     }
 
@@ -81,6 +84,7 @@ impl EntryViewState {
                             self.project.clone(),
                             self.history_store.clone(),
                             self.prompt_store.clone(),
+                            self.prompt_capabilities.clone(),
                             "Edit message - @ to include context",
                             self.prevent_slash_commands,
                             editor::EditorMode::AutoHeight {
@@ -403,6 +407,7 @@ mod tests {
                 project.clone(),
                 history_store,
                 None,
+                Default::default(),
                 false,
             )
         });

crates/agent_ui/src/acp/message_editor.rs 🔗

@@ -87,6 +87,7 @@ impl MessageEditor {
         project: Entity<Project>,
         history_store: Entity<HistoryStore>,
         prompt_store: Option<Entity<PromptStore>>,
+        prompt_capabilities: Rc<Cell<acp::PromptCapabilities>>,
         placeholder: impl Into<Arc<str>>,
         prevent_slash_commands: bool,
         mode: EditorMode,
@@ -100,7 +101,6 @@ impl MessageEditor {
             },
             None,
         );
-        let prompt_capabilities = Rc::new(Cell::new(acp::PromptCapabilities::default()));
         let completion_provider = ContextPickerCompletionProvider::new(
             cx.weak_entity(),
             workspace.clone(),
@@ -203,10 +203,6 @@ impl MessageEditor {
         .detach();
     }
 
-    pub fn set_prompt_capabilities(&mut self, capabilities: acp::PromptCapabilities) {
-        self.prompt_capabilities.set(capabilities);
-    }
-
     #[cfg(test)]
     pub(crate) fn editor(&self) -> &Entity<Editor> {
         &self.editor
@@ -1095,15 +1091,21 @@ impl MessageEditor {
                         mentions.push((start..end, mention_uri, resource.text));
                     }
                 }
+                acp::ContentBlock::ResourceLink(resource) => {
+                    if let Some(mention_uri) = MentionUri::parse(&resource.uri).log_err() {
+                        let start = text.len();
+                        write!(&mut text, "{}", mention_uri.as_link()).ok();
+                        let end = text.len();
+                        mentions.push((start..end, mention_uri, resource.uri));
+                    }
+                }
                 acp::ContentBlock::Image(content) => {
                     let start = text.len();
                     text.push_str("image");
                     let end = text.len();
                     images.push((start..end, content));
                 }
-                acp::ContentBlock::Audio(_)
-                | acp::ContentBlock::Resource(_)
-                | acp::ContentBlock::ResourceLink(_) => {}
+                acp::ContentBlock::Audio(_) | acp::ContentBlock::Resource(_) => {}
             }
         }
 
@@ -1850,7 +1852,7 @@ impl Addon for MessageEditorAddon {
 
 #[cfg(test)]
 mod tests {
-    use std::{ops::Range, path::Path, sync::Arc};
+    use std::{cell::Cell, ops::Range, path::Path, rc::Rc, sync::Arc};
 
     use acp_thread::MentionUri;
     use agent_client_protocol as acp;
@@ -1896,6 +1898,7 @@ mod tests {
                     project.clone(),
                     history_store.clone(),
                     None,
+                    Default::default(),
                     "Test",
                     false,
                     EditorMode::AutoHeight {
@@ -2086,6 +2089,7 @@ mod tests {
 
         let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
         let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
+        let prompt_capabilities = Rc::new(Cell::new(acp::PromptCapabilities::default()));
 
         let (message_editor, editor) = workspace.update_in(&mut cx, |workspace, window, cx| {
             let workspace_handle = cx.weak_entity();
@@ -2095,6 +2099,7 @@ mod tests {
                     project.clone(),
                     history_store.clone(),
                     None,
+                    prompt_capabilities.clone(),
                     "Test",
                     false,
                     EditorMode::AutoHeight {
@@ -2139,13 +2144,10 @@ mod tests {
             editor.set_text("", window, cx);
         });
 
-        message_editor.update(&mut cx, |editor, _cx| {
-            // Enable all prompt capabilities
-            editor.set_prompt_capabilities(acp::PromptCapabilities {
-                image: true,
-                audio: true,
-                embedded_context: true,
-            });
+        prompt_capabilities.set(acp::PromptCapabilities {
+            image: true,
+            audio: true,
+            embedded_context: true,
         });
 
         cx.simulate_input("Lorem ");

crates/agent_ui/src/acp/thread_view.rs 🔗

@@ -5,7 +5,7 @@ use acp_thread::{
 };
 use acp_thread::{AgentConnection, Plan};
 use action_log::ActionLog;
-use agent_client_protocol::{self as acp};
+use agent_client_protocol::{self as acp, PromptCapabilities};
 use agent_servers::{AgentServer, ClaudeCode};
 use agent_settings::{AgentProfileId, AgentSettings, CompletionMode, NotifyWhenAgentWaiting};
 use agent2::{DbThreadMetadata, HistoryEntry, HistoryEntryId, HistoryStore};
@@ -34,6 +34,7 @@ use project::{Project, ProjectEntryId};
 use prompt_store::{PromptId, PromptStore};
 use rope::Point;
 use settings::{Settings as _, SettingsStore};
+use std::cell::Cell;
 use std::sync::Arc;
 use std::time::Instant;
 use std::{collections::BTreeMap, rc::Rc, time::Duration};
@@ -271,6 +272,7 @@ pub struct AcpThreadView {
     plan_expanded: bool,
     editor_expanded: bool,
     editing_message: Option<usize>,
+    prompt_capabilities: Rc<Cell<PromptCapabilities>>,
     _cancel_task: Option<Task<()>>,
     _subscriptions: [Subscription; 3],
 }
@@ -306,6 +308,7 @@ impl AcpThreadView {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> Self {
+        let prompt_capabilities = Rc::new(Cell::new(acp::PromptCapabilities::default()));
         let prevent_slash_commands = agent.clone().downcast::<ClaudeCode>().is_some();
         let message_editor = cx.new(|cx| {
             let mut editor = MessageEditor::new(
@@ -313,6 +316,7 @@ impl AcpThreadView {
                 project.clone(),
                 history_store.clone(),
                 prompt_store.clone(),
+                prompt_capabilities.clone(),
                 "Message the agent — @ to include context",
                 prevent_slash_commands,
                 editor::EditorMode::AutoHeight {
@@ -336,6 +340,7 @@ impl AcpThreadView {
                 project.clone(),
                 history_store.clone(),
                 prompt_store.clone(),
+                prompt_capabilities.clone(),
                 prevent_slash_commands,
             )
         });
@@ -371,6 +376,7 @@ impl AcpThreadView {
             editor_expanded: false,
             history_store,
             hovered_recent_history_item: None,
+            prompt_capabilities,
             _subscriptions: subscriptions,
             _cancel_task: None,
         }
@@ -448,6 +454,9 @@ impl AcpThreadView {
                     Ok(thread) => {
                         let action_log = thread.read(cx).action_log().clone();
 
+                        this.prompt_capabilities
+                            .set(connection.prompt_capabilities());
+
                         let count = thread.read(cx).entries().len();
                         this.list_state.splice(0..0, count);
                         this.entry_view_state.update(cx, |view_state, cx| {
@@ -523,11 +532,6 @@ impl AcpThreadView {
                             })
                         });
 
-                        this.message_editor.update(cx, |message_editor, _cx| {
-                            message_editor
-                                .set_prompt_capabilities(connection.prompt_capabilities());
-                        });
-
                         cx.notify();
                     }
                     Err(err) => {