diff --git a/crates/acp_thread/src/acp_thread.rs b/crates/acp_thread/src/acp_thread.rs index d0e8860084acd0a4dba7daadb000ed1f80033cf2..46797130c0cebb605c6820f4d04fd2e989977617 100644 --- a/crates/acp_thread/src/acp_thread.rs +++ b/crates/acp_thread/src/acp_thread.rs @@ -1713,6 +1713,7 @@ impl AcpThread { pub fn resolve_locations(&mut self, id: acp::ToolCallId, cx: &mut Context) { let project = self.project.clone(); + let should_update_agent_location = self.parent_session_id.is_none(); let Some((_, tool_call)) = self.tool_call_mut(&id) else { return; }; @@ -1748,7 +1749,7 @@ impl AcpThread { } else { false }; - if !should_ignore { + if !should_ignore && should_update_agent_location { project.set_agent_location(Some(location.into()), cx); } }); @@ -1979,8 +1980,10 @@ impl AcpThread { .await?; this.update(cx, |this, cx| { - this.project - .update(cx, |project, cx| project.set_agent_location(None, cx)); + if this.parent_session_id.is_none() { + this.project + .update(cx, |project, cx| project.set_agent_location(None, cx)); + } let Ok(response) = response else { // tx dropped, just return return Ok(None); @@ -2252,6 +2255,7 @@ impl AcpThread { let limit = limit.unwrap_or(u32::MAX); let project = self.project.clone(); let action_log = self.action_log.clone(); + let should_update_agent_location = self.parent_session_id.is_none(); cx.spawn(async move |this, cx| { let load = project.update(cx, |project, cx| { let path = project @@ -2302,15 +2306,17 @@ impl AcpThread { let start = snapshot.anchor_before(start_position); let end = snapshot.anchor_before(Point::new(line.saturating_add(limit), 0)); - project.update(cx, |project, cx| { - project.set_agent_location( - Some(AgentLocation { - buffer: buffer.downgrade(), - position: start, - }), - cx, - ); - }); + if should_update_agent_location { + project.update(cx, |project, cx| { + project.set_agent_location( + Some(AgentLocation { + buffer: buffer.downgrade(), + position: start, + }), + cx, + ); + }); + } Ok(snapshot.text_for_range(start..end).collect::()) }) @@ -2324,6 +2330,7 @@ impl AcpThread { ) -> Task> { let project = self.project.clone(); let action_log = self.action_log.clone(); + let should_update_agent_location = self.parent_session_id.is_none(); cx.spawn(async move |this, cx| { let load = project.update(cx, |project, cx| { let path = project @@ -2351,18 +2358,20 @@ impl AcpThread { }) .await; - project.update(cx, |project, cx| { - project.set_agent_location( - Some(AgentLocation { - buffer: buffer.downgrade(), - position: edits - .last() - .map(|(range, _)| range.end) - .unwrap_or(Anchor::min_for_buffer(buffer.read(cx).remote_id())), - }), - cx, - ); - }); + if should_update_agent_location { + project.update(cx, |project, cx| { + project.set_agent_location( + Some(AgentLocation { + buffer: buffer.downgrade(), + position: edits + .last() + .map(|(range, _)| range.end) + .unwrap_or(Anchor::min_for_buffer(buffer.read(cx).remote_id())), + }), + cx, + ); + }); + } let format_on_save = cx.update(|cx| { action_log.update(cx, |action_log, cx| { diff --git a/crates/agent/src/edit_agent.rs b/crates/agent/src/edit_agent.rs index ef95eee07378438686aff688fdaf2d7fa98e036b..e122d6b2884a593daa819457835d3d00690f5a7d 100644 --- a/crates/agent/src/edit_agent.rs +++ b/crates/agent/src/edit_agent.rs @@ -84,6 +84,7 @@ pub struct EditAgent { templates: Arc, edit_format: EditFormat, thinking_allowed: bool, + update_agent_location: bool, } impl EditAgent { @@ -94,6 +95,7 @@ impl EditAgent { templates: Arc, edit_format: EditFormat, allow_thinking: bool, + update_agent_location: bool, ) -> Self { EditAgent { model, @@ -102,6 +104,7 @@ impl EditAgent { templates, edit_format, thinking_allowed: allow_thinking, + update_agent_location, } } @@ -170,15 +173,17 @@ impl EditAgent { ) -> Result<()> { let buffer_id = cx.update(|cx| { let buffer_id = buffer.read(cx).remote_id(); - self.project.update(cx, |project, cx| { - project.set_agent_location( - Some(AgentLocation { - buffer: buffer.downgrade(), - position: language::Anchor::min_for_buffer(buffer_id), - }), - cx, - ) - }); + if self.update_agent_location { + self.project.update(cx, |project, cx| { + project.set_agent_location( + Some(AgentLocation { + buffer: buffer.downgrade(), + position: language::Anchor::min_for_buffer(buffer_id), + }), + cx, + ) + }); + } buffer_id }); @@ -190,15 +195,17 @@ impl EditAgent { .ok() }; let set_agent_location = |cx: &mut _| { - self.project.update(cx, |project, cx| { - project.set_agent_location( - Some(AgentLocation { - buffer: buffer.downgrade(), - position: language::Anchor::max_for_buffer(buffer_id), - }), - cx, - ) - }) + if self.update_agent_location { + self.project.update(cx, |project, cx| { + project.set_agent_location( + Some(AgentLocation { + buffer: buffer.downgrade(), + position: language::Anchor::max_for_buffer(buffer_id), + }), + cx, + ) + }) + } }; let mut first_chunk = true; while let Some(event) = parse_rx.next().await { @@ -302,15 +309,17 @@ impl EditAgent { if let Some(old_range) = old_range { let old_range = snapshot.anchor_before(old_range.start) ..snapshot.anchor_before(old_range.end); - self.project.update(cx, |project, cx| { - project.set_agent_location( - Some(AgentLocation { - buffer: buffer.downgrade(), - position: old_range.end, - }), - cx, - ); - }); + if self.update_agent_location { + self.project.update(cx, |project, cx| { + project.set_agent_location( + Some(AgentLocation { + buffer: buffer.downgrade(), + position: old_range.end, + }), + cx, + ); + }); + } output_events .unbounded_send(EditAgentOutputEvent::ResolvingEditRange(old_range)) .ok(); @@ -383,15 +392,17 @@ impl EditAgent { }); self.action_log .update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx)); - self.project.update(cx, |project, cx| { - project.set_agent_location( - Some(AgentLocation { - buffer: buffer.downgrade(), - position: max_edit_end, - }), - cx, - ); - }); + if self.update_agent_location { + self.project.update(cx, |project, cx| { + project.set_agent_location( + Some(AgentLocation { + buffer: buffer.downgrade(), + position: max_edit_end, + }), + cx, + ); + }); + } (min_edit_start, max_edit_end) }); output_events @@ -1390,6 +1401,7 @@ mod tests { Templates::new(), EditFormat::XmlTags, thinking_allowed, + true, ) } diff --git a/crates/agent/src/edit_agent/evals.rs b/crates/agent/src/edit_agent/evals.rs index cdf6c1c0b3f6440e4827c8b74b47a32d997b092f..2e8818b101995b374cf8172547c45b55c27c6f26 100644 --- a/crates/agent/src/edit_agent/evals.rs +++ b/crates/agent/src/edit_agent/evals.rs @@ -1469,6 +1469,7 @@ impl EditAgentTest { Templates::new(), edit_format, true, + true, ), project, judge_model, diff --git a/crates/agent/src/tools/edit_file_tool.rs b/crates/agent/src/tools/edit_file_tool.rs index 3e1e0661f126d464c8d4611e2b3d85a9f668a5ca..b680e3b885f7d002657ee4b0bc384d6d9afaa055 100644 --- a/crates/agent/src/tools/edit_file_tool.rs +++ b/crates/agent/src/tools/edit_file_tool.rs @@ -253,7 +253,7 @@ impl AgentTool for EditFileTool { error: "thread was dropped".to_string(), })?; - let (project_path, abs_path, allow_thinking, authorize) = + let (project_path, abs_path, allow_thinking, update_agent_location, authorize) = cx.update(|cx| { let project_path = resolve_path(&input, project.clone(), cx).map_err(|err| { EditFileToolOutput::Error { @@ -271,8 +271,11 @@ impl AgentTool for EditFileTool { .thread .read_with(cx, |thread, _cx| thread.thinking_enabled()) .unwrap_or(true); + + let update_agent_location = self.thread.read_with(cx, |thread, _cx| !thread.is_subagent()).unwrap_or_default(); + let authorize = self.authorize(&input, &event_stream, cx); - Ok::<_, EditFileToolOutput>((project_path, abs_path, allow_thinking, authorize)) + Ok::<_, EditFileToolOutput>((project_path, abs_path, allow_thinking, update_agent_location, authorize)) })?; let result: anyhow::Result = async { @@ -293,6 +296,7 @@ impl AgentTool for EditFileTool { self.templates.clone(), edit_format, allow_thinking, + update_agent_location, ); let buffer = project diff --git a/crates/agent/src/tools/read_file_tool.rs b/crates/agent/src/tools/read_file_tool.rs index bbc67cf68c7d104772c18ad222478621ce4d7a54..8cfc16ddf6174a190ffe7cc11921dc204b05b79d 100644 --- a/crates/agent/src/tools/read_file_tool.rs +++ b/crates/agent/src/tools/read_file_tool.rs @@ -212,7 +212,6 @@ impl AgentTool for ReadFileTool { }); if is_image { - let image_entity: Entity = cx .update(|cx| { self.project.update(cx, |project, cx| { @@ -269,6 +268,9 @@ impl AgentTool for ReadFileTool { .ok(); } + + let update_agent_location = self.thread.read_with(cx, |thread, _cx| !thread.is_subagent()).unwrap_or_default(); + let mut anchor = None; // Check if specific line ranges are provided @@ -328,15 +330,17 @@ impl AgentTool for ReadFileTool { }; project.update(cx, |project, cx| { - project.set_agent_location( - Some(AgentLocation { - buffer: buffer.downgrade(), - position: anchor.unwrap_or_else(|| { - text::Anchor::min_for_buffer(buffer.read(cx).remote_id()) + if update_agent_location { + project.set_agent_location( + Some(AgentLocation { + buffer: buffer.downgrade(), + position: anchor.unwrap_or_else(|| { + text::Anchor::min_for_buffer(buffer.read(cx).remote_id()) + }), }), - }), - cx, - ); + cx, + ); + } if let Ok(LanguageModelToolResultContent::Text(text)) = &result { let text: &str = text; let markdown = MarkdownCodeBlock { diff --git a/crates/agent/src/tools/streaming_edit_file_tool.rs b/crates/agent/src/tools/streaming_edit_file_tool.rs index 2658e372d77044b60648d8fab39e458f02dba23d..a0d6d3a374e3b64c6652e089efe8de31b645b052 100644 --- a/crates/agent/src/tools/streaming_edit_file_tool.rs +++ b/crates/agent/src/tools/streaming_edit_file_tool.rs @@ -220,9 +220,15 @@ impl StreamingEditFileTool { } fn set_agent_location(&self, buffer: WeakEntity, position: text::Anchor, cx: &mut App) { - self.project.update(cx, |project, cx| { - project.set_agent_location(Some(AgentLocation { buffer, position }), cx); - }); + let should_update_agent_location = self + .thread + .read_with(cx, |thread, _cx| !thread.is_subagent()) + .unwrap_or_default(); + if should_update_agent_location { + self.project.update(cx, |project, cx| { + project.set_agent_location(Some(AgentLocation { buffer, position }), cx); + }); + } } } diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs index 967b53bd200e6dc8e863a86602b2ac5f590406e2..ba188ccb592871c62c6f010f026a8948c8cf89fa 100644 --- a/crates/agent_ui/src/agent_ui.rs +++ b/crates/agent_ui/src/agent_ui.rs @@ -179,18 +179,6 @@ pub struct AuthorizeToolCall { pub option_kind: String, } -/// Action to select a permission granularity option from the dropdown. -/// This updates the selected granularity without triggering authorization. -#[derive(Clone, PartialEq, Deserialize, JsonSchema, Action)] -#[action(namespace = agent)] -#[serde(deny_unknown_fields)] -pub struct SelectPermissionGranularity { - /// The tool call ID for which to select the granularity. - pub tool_call_id: String, - /// The index of the selected granularity option. - pub index: usize, -} - /// Creates a new conversation thread, optionally based on an existing thread. #[derive(Default, Clone, PartialEq, Deserialize, JsonSchema, Action)] #[action(namespace = agent)] diff --git a/crates/agent_ui/src/connection_view.rs b/crates/agent_ui/src/connection_view.rs index 96b4b69eb24339003d2ce31d33ccf15437b906f3..a3a62459a2e98680b3910877cc9cd1e6e58ba056 100644 --- a/crates/agent_ui/src/connection_view.rs +++ b/crates/agent_ui/src/connection_view.rs @@ -74,9 +74,9 @@ use crate::{ AgentDiffPane, AgentInitialContent, AgentPanel, AllowAlways, AllowOnce, AuthorizeToolCall, ClearMessageQueue, CycleFavoriteModels, CycleModeSelector, CycleThinkingEffort, EditFirstQueuedMessage, ExpandMessageEditor, Follow, KeepAll, NewThread, OpenAddContextMenu, - OpenAgentDiff, OpenHistory, RejectAll, RejectOnce, RemoveFirstQueuedMessage, - SelectPermissionGranularity, SendImmediately, SendNextQueuedMessage, ToggleFastMode, - ToggleProfileSelector, ToggleThinkingEffortMenu, ToggleThinkingMode, UndoLastReject, + OpenAgentDiff, OpenHistory, RejectAll, RejectOnce, RemoveFirstQueuedMessage, SendImmediately, + SendNextQueuedMessage, ToggleFastMode, ToggleProfileSelector, ToggleThinkingEffortMenu, + ToggleThinkingMode, UndoLastReject, }; const STOPWATCH_THRESHOLD: Duration = Duration::from_secs(30); @@ -155,6 +155,9 @@ pub(crate) struct Conversation { threads: HashMap>, permission_requests: IndexMap>, subscriptions: Vec, + /// Tracks the selected granularity index for each tool call's permission dropdown. + /// The index corresponds to the position in the allow_options list. + selected_permission_granularity: HashMap>, } impl Conversation { @@ -196,6 +199,29 @@ impl Conversation { .insert(thread.read(cx).session_id().clone(), thread); } + pub fn selected_permission_granularity( + &self, + session_id: &acp::SessionId, + tool_call_id: &acp::ToolCallId, + ) -> Option { + self.selected_permission_granularity + .get(session_id) + .and_then(|map| map.get(tool_call_id)) + .copied() + } + + pub fn set_selected_permission_granularity( + &mut self, + session_id: acp::SessionId, + tool_call_id: acp::ToolCallId, + granularity: usize, + ) { + self.selected_permission_granularity + .entry(session_id) + .or_default() + .insert(tool_call_id, granularity); + } + pub fn pending_tool_call<'a>( &'a self, session_id: &acp::SessionId, @@ -5580,182 +5606,6 @@ pub(crate) mod tests { }); } - #[gpui::test] - async fn test_granularity_selection_updates_state(cx: &mut TestAppContext) { - init_test(cx); - - let tool_call_id = acp::ToolCallId::new("granularity-test-1"); - let tool_call = - acp::ToolCall::new(tool_call_id.clone(), "Run `cargo build`").kind(acp::ToolKind::Edit); - - let permission_options = - ToolPermissionContext::new(TerminalTool::NAME, vec!["cargo build".to_string()]) - .build_permission_options(); - - let connection = - StubAgentConnection::new().with_permission_requests(HashMap::from_iter([( - tool_call_id.clone(), - permission_options.clone(), - )])); - - connection.set_next_prompt_updates(vec![acp::SessionUpdate::ToolCall(tool_call)]); - - let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await; - add_to_workspace(thread_view.clone(), cx); - - cx.update(|_window, cx| { - AgentSettings::override_global( - AgentSettings { - notify_when_agent_waiting: NotifyWhenAgentWaiting::Never, - ..AgentSettings::get_global(cx).clone() - }, - cx, - ); - }); - - let message_editor = message_editor(&thread_view, cx); - message_editor.update_in(cx, |editor, window, cx| { - editor.set_text("Build the project", window, cx); - }); - - active_thread(&thread_view, cx).update_in(cx, |view, window, cx| view.send(window, cx)); - - cx.run_until_parked(); - - // Verify default granularity is the last option (index 2 = "Only this time") - thread_view.read_with(cx, |thread_view, cx| { - let state = thread_view.active_thread().unwrap(); - let selected = state - .read(cx) - .selected_permission_granularity - .get(&tool_call_id); - assert!( - selected.is_none(), - "Should have no selection initially (defaults to last)" - ); - }); - - // Select the first option (index 0 = "Always for terminal") - thread_view.update_in(cx, |_, window, cx| { - window.dispatch_action( - crate::SelectPermissionGranularity { - tool_call_id: "granularity-test-1".to_string(), - index: 0, - } - .boxed_clone(), - cx, - ); - }); - - cx.run_until_parked(); - - // Verify the selection was updated - thread_view.read_with(cx, |thread_view, cx| { - let state = thread_view.active_thread().unwrap(); - let selected = state - .read(cx) - .selected_permission_granularity - .get(&tool_call_id); - assert_eq!(selected, Some(&0), "Should have selected index 0"); - }); - } - - #[gpui::test] - async fn test_allow_button_uses_selected_granularity(cx: &mut TestAppContext) { - init_test(cx); - - let tool_call_id = acp::ToolCallId::new("allow-granularity-test-1"); - let tool_call = - acp::ToolCall::new(tool_call_id.clone(), "Run `npm install`").kind(acp::ToolKind::Edit); - - let permission_options = - ToolPermissionContext::new(TerminalTool::NAME, vec!["npm install".to_string()]) - .build_permission_options(); - - // Verify we have the expected options - let PermissionOptions::Dropdown(choices) = &permission_options else { - panic!("Expected dropdown permission options"); - }; - - assert_eq!(choices.len(), 3); - assert!( - choices[0] - .allow - .option_id - .0 - .contains("always_allow:terminal") - ); - assert!( - choices[1] - .allow - .option_id - .0 - .contains("always_allow_pattern:terminal") - ); - assert_eq!(choices[2].allow.option_id.0.as_ref(), "allow"); - - let connection = - StubAgentConnection::new().with_permission_requests(HashMap::from_iter([( - tool_call_id.clone(), - permission_options.clone(), - )])); - - connection.set_next_prompt_updates(vec![acp::SessionUpdate::ToolCall(tool_call)]); - - let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await; - add_to_workspace(thread_view.clone(), cx); - - cx.update(|_window, cx| { - AgentSettings::override_global( - AgentSettings { - notify_when_agent_waiting: NotifyWhenAgentWaiting::Never, - ..AgentSettings::get_global(cx).clone() - }, - cx, - ); - }); - - let message_editor = message_editor(&thread_view, cx); - message_editor.update_in(cx, |editor, window, cx| { - editor.set_text("Install dependencies", window, cx); - }); - - active_thread(&thread_view, cx).update_in(cx, |view, window, cx| view.send(window, cx)); - - cx.run_until_parked(); - - // Select the pattern option (index 1 = "Always for `npm` commands") - thread_view.update_in(cx, |_, window, cx| { - window.dispatch_action( - crate::SelectPermissionGranularity { - tool_call_id: "allow-granularity-test-1".to_string(), - index: 1, - } - .boxed_clone(), - cx, - ); - }); - - cx.run_until_parked(); - - // Simulate clicking the Allow button by dispatching AllowOnce action - // which should use the selected granularity - active_thread(&thread_view, cx).update_in(cx, |view, window, cx| { - view.allow_once(&AllowOnce, window, cx) - }); - - cx.run_until_parked(); - - // Verify tool call was authorized - thread_view.read_with(cx, |thread_view, cx| { - let tool_call = thread_view.pending_tool_call(cx); - assert!( - tool_call.is_none(), - "Tool call should be authorized after Allow with pattern granularity" - ); - }); - } - #[gpui::test] async fn test_deny_button_uses_selected_granularity(cx: &mut TestAppContext) { init_test(cx); diff --git a/crates/agent_ui/src/connection_view/thread_view.rs b/crates/agent_ui/src/connection_view/thread_view.rs index 8a29d16b1acf165ba77093dced980a7f51fe2e37..f9d5311983c5f1a0b53504fc88b97ef8f2e953c4 100644 --- a/crates/agent_ui/src/connection_view/thread_view.rs +++ b/crates/agent_ui/src/connection_view/thread_view.rs @@ -235,10 +235,6 @@ pub struct ThreadView { pub is_loading_contents: bool, pub new_server_version_available: Option, pub resumed_without_history: bool, - /// Tracks the selected granularity index for each tool call's permission dropdown. - /// The index corresponds to the position in the allow_options list. - /// Default is the last option (index pointing to "Only this time"). - pub selected_permission_granularity: HashMap, pub resume_thread_metadata: Option, pub _cancel_task: Option>, pub skip_queue_processing_count: usize, @@ -428,7 +424,6 @@ impl ThreadView { discarded_partial_edits: HashSet::default(), is_loading_contents: false, new_server_version_available: None, - selected_permission_granularity: HashMap::default(), _cancel_task: None, skip_queue_processing_count: 0, user_interrupted_generation: false, @@ -1385,19 +1380,6 @@ impl ThreadView { ); } - pub fn handle_select_permission_granularity( - &mut self, - action: &SelectPermissionGranularity, - _window: &mut Window, - cx: &mut Context, - ) { - let tool_call_id = acp::ToolCallId::new(action.tool_call_id.clone()); - self.selected_permission_granularity - .insert(tool_call_id, action.index); - - cx.notify(); - } - fn authorize_pending_with_granularity( &mut self, is_allow: bool, @@ -1417,9 +1399,9 @@ impl ThreadView { // Get selected index, defaulting to last option ("Only this time") let selected_index = self - .selected_permission_granularity - .get(&tool_call_id) - .copied() + .conversation + .read(cx) + .selected_permission_granularity(&session_id, &tool_call_id) .unwrap_or_else(|| choices.len().saturating_sub(1)); let selected_choice = choices.get(selected_index).or(choices.last())?; @@ -1817,23 +1799,26 @@ impl ThreadView { .when(!plan.is_empty() && !changed_buffers.is_empty(), |this| { this.child(Divider::horizontal().color(DividerColor::Border)) }) - .when(!changed_buffers.is_empty(), |this| { - this.child(self.render_edits_summary( - &changed_buffers, - edits_expanded, - pending_edits, - cx, - )) - .when(edits_expanded, |parent| { - parent.child(self.render_edited_files( - action_log, - telemetry.clone(), + .when( + !changed_buffers.is_empty() && thread.parent_session_id().is_none(), + |this| { + this.child(self.render_edits_summary( &changed_buffers, + edits_expanded, pending_edits, cx, )) - }) - }) + .when(edits_expanded, |parent| { + parent.child(self.render_edited_files( + action_log, + telemetry.clone(), + &changed_buffers, + pending_edits, + cx, + )) + }) + }, + ) .when(!queue_is_empty, |this| { this.when(!plan.is_empty() || !changed_buffers.is_empty(), |this| { this.child(Divider::horizontal().color(DividerColor::Border)) @@ -5518,9 +5503,9 @@ impl ThreadView { ) -> Div { // Get the selected granularity index, defaulting to the last option ("Only this time") let selected_index = self - .selected_permission_granularity - .get(&tool_call_id) - .copied() + .conversation + .read(cx) + .selected_permission_granularity(&session_id, &tool_call_id) .unwrap_or_else(|| choices.len().saturating_sub(1)); let selected_choice = choices.get(selected_index).or(choices.last()); @@ -5608,6 +5593,7 @@ impl ThreadView { ) }) .on_click(cx.listener({ + let session_id = session_id.clone(); let tool_call_id = tool_call_id.clone(); let option_id = deny_option_id; let option_kind = deny_option_kind; @@ -5628,6 +5614,7 @@ impl ThreadView { choices, dropdown_label, entry_ix, + session_id, tool_call_id, selected_index, is_first, @@ -5640,6 +5627,7 @@ impl ThreadView { choices: &[PermissionOptionChoice], current_label: SharedString, entry_ix: usize, + session_id: acp::SessionId, tool_call_id: acp::ToolCallId, selected_index: usize, is_first: bool, @@ -5653,6 +5641,8 @@ impl ThreadView { let permission_dropdown_handle = self.permission_dropdown_handle.clone(); + let conversation = self.conversation.clone(); + PopoverMenu::new(("permission-granularity", entry_ix)) .with_handle(permission_dropdown_handle) .trigger( @@ -5673,6 +5663,8 @@ impl ThreadView { }), ) .menu(move |window, cx| { + let session_id = session_id.clone(); + let conversation = conversation.clone(); let tool_call_id = tool_call_id.clone(); let options = menu_options.clone(); @@ -5680,23 +5672,23 @@ impl ThreadView { for (index, display_name) in options.iter() { let display_name = display_name.clone(); let index = *index; - let tool_call_id_for_entry = tool_call_id.clone(); + let session_id = session_id.clone(); + let conversation = conversation.clone(); + let tool_call_id = tool_call_id.clone(); let is_selected = index == selected_index; - menu = menu.toggleable_entry( display_name, is_selected, IconPosition::End, None, - move |window, cx| { - window.dispatch_action( - SelectPermissionGranularity { - tool_call_id: tool_call_id_for_entry.0.to_string(), + move |_window, cx| { + conversation.update(cx, |conversation, _cx| { + conversation.set_selected_permission_granularity( + session_id.clone(), + tool_call_id.clone(), index, - } - .boxed_clone(), - cx, - ); + ); + }); }, ); } @@ -7520,7 +7512,6 @@ impl Render for ThreadView { .on_action(cx.listener(Self::allow_once)) .on_action(cx.listener(Self::reject_once)) .on_action(cx.listener(Self::handle_authorize_tool_call)) - .on_action(cx.listener(Self::handle_select_permission_granularity)) .on_action(cx.listener(Self::open_permission_dropdown)) .on_action(cx.listener(Self::open_add_context_menu)) .on_action(cx.listener(|this, _: &ToggleFastMode, _window, cx| {