claude: Respect always allow setting (#36450)

Claude will now respect the `agent.always_allow_tool_actions` setting
and will set it when "Always Allow" is clicked.

Release Notes:

- N/A
This commit is contained in:
Agus Zubiaga 2025-08-18 20:27:08 -03:00 committed by GitHub
parent 33fbe53d48
commit b578031120
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 64 additions and 11 deletions

1
Cargo.lock generated
View file

@ -258,6 +258,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"acp_thread", "acp_thread",
"agent-client-protocol", "agent-client-protocol",
"agent_settings",
"agentic-coding-protocol", "agentic-coding-protocol",
"anyhow", "anyhow",
"collections", "collections",

View file

@ -19,6 +19,7 @@ doctest = false
[dependencies] [dependencies]
acp_thread.workspace = true acp_thread.workspace = true
agent-client-protocol.workspace = true agent-client-protocol.workspace = true
agent_settings.workspace = true
agentic-coding-protocol.workspace = true agentic-coding-protocol.workspace = true
anyhow.workspace = true anyhow.workspace = true
collections.workspace = true collections.workspace = true

View file

@ -111,7 +111,8 @@ impl AgentConnection for ClaudeAgentConnection {
})?; })?;
let (mut thread_tx, thread_rx) = watch::channel(WeakEntity::new_invalid()); let (mut thread_tx, thread_rx) = watch::channel(WeakEntity::new_invalid());
let permission_mcp_server = ClaudeZedMcpServer::new(thread_rx.clone(), cx).await?; let fs = project.read_with(cx, |project, _cx| project.fs().clone())?;
let permission_mcp_server = ClaudeZedMcpServer::new(thread_rx.clone(), fs, cx).await?;
let mut mcp_servers = HashMap::default(); let mut mcp_servers = HashMap::default();
mcp_servers.insert( mcp_servers.insert(

View file

@ -1,8 +1,10 @@
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc;
use crate::claude::tools::{ClaudeTool, EditToolParams, ReadToolParams}; use crate::claude::tools::{ClaudeTool, EditToolParams, ReadToolParams};
use acp_thread::AcpThread; use acp_thread::AcpThread;
use agent_client_protocol as acp; use agent_client_protocol as acp;
use agent_settings::AgentSettings;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use collections::HashMap; use collections::HashMap;
use context_server::listener::{McpServerTool, ToolResponse}; use context_server::listener::{McpServerTool, ToolResponse};
@ -11,8 +13,11 @@ use context_server::types::{
ToolAnnotations, ToolResponseContent, ToolsCapabilities, requests, ToolAnnotations, ToolResponseContent, ToolsCapabilities, requests,
}; };
use gpui::{App, AsyncApp, Task, WeakEntity}; use gpui::{App, AsyncApp, Task, WeakEntity};
use project::Fs;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings as _, update_settings_file};
use util::debug_panic;
pub struct ClaudeZedMcpServer { pub struct ClaudeZedMcpServer {
server: context_server::listener::McpServer, server: context_server::listener::McpServer,
@ -23,6 +28,7 @@ pub const SERVER_NAME: &str = "zed";
impl ClaudeZedMcpServer { impl ClaudeZedMcpServer {
pub async fn new( pub async fn new(
thread_rx: watch::Receiver<WeakEntity<AcpThread>>, thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
fs: Arc<dyn Fs>,
cx: &AsyncApp, cx: &AsyncApp,
) -> Result<Self> { ) -> Result<Self> {
let mut mcp_server = context_server::listener::McpServer::new(cx).await?; let mut mcp_server = context_server::listener::McpServer::new(cx).await?;
@ -30,6 +36,7 @@ impl ClaudeZedMcpServer {
mcp_server.add_tool(PermissionTool { mcp_server.add_tool(PermissionTool {
thread_rx: thread_rx.clone(), thread_rx: thread_rx.clone(),
fs: fs.clone(),
}); });
mcp_server.add_tool(ReadTool { mcp_server.add_tool(ReadTool {
thread_rx: thread_rx.clone(), thread_rx: thread_rx.clone(),
@ -102,6 +109,7 @@ pub struct McpServerConfig {
#[derive(Clone)] #[derive(Clone)]
pub struct PermissionTool { pub struct PermissionTool {
fs: Arc<dyn Fs>,
thread_rx: watch::Receiver<WeakEntity<AcpThread>>, thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
} }
@ -141,6 +149,24 @@ impl McpServerTool for PermissionTool {
input: Self::Input, input: Self::Input,
cx: &mut AsyncApp, cx: &mut AsyncApp,
) -> Result<ToolResponse<Self::Output>> { ) -> Result<ToolResponse<Self::Output>> {
if agent_settings::AgentSettings::try_read_global(cx, |settings| {
settings.always_allow_tool_actions
})
.unwrap_or(false)
{
let response = PermissionToolResponse {
behavior: PermissionToolBehavior::Allow,
updated_input: input.input,
};
return Ok(ToolResponse {
content: vec![ToolResponseContent::Text {
text: serde_json::to_string(&response)?,
}],
structured_content: (),
});
}
let mut thread_rx = self.thread_rx.clone(); let mut thread_rx = self.thread_rx.clone();
let Some(thread) = thread_rx.recv().await?.upgrade() else { let Some(thread) = thread_rx.recv().await?.upgrade() else {
anyhow::bail!("Thread closed"); anyhow::bail!("Thread closed");
@ -148,8 +174,10 @@ impl McpServerTool for PermissionTool {
let claude_tool = ClaudeTool::infer(&input.tool_name, input.input.clone()); let claude_tool = ClaudeTool::infer(&input.tool_name, input.input.clone());
let tool_call_id = acp::ToolCallId(input.tool_use_id.context("Tool ID required")?.into()); let tool_call_id = acp::ToolCallId(input.tool_use_id.context("Tool ID required")?.into());
let allow_option_id = acp::PermissionOptionId("allow".into());
let reject_option_id = acp::PermissionOptionId("reject".into()); const ALWAYS_ALLOW: &'static str = "always_allow";
const ALLOW: &'static str = "allow";
const REJECT: &'static str = "reject";
let chosen_option = thread let chosen_option = thread
.update(cx, |thread, cx| { .update(cx, |thread, cx| {
@ -157,12 +185,17 @@ impl McpServerTool for PermissionTool {
claude_tool.as_acp(tool_call_id).into(), claude_tool.as_acp(tool_call_id).into(),
vec![ vec![
acp::PermissionOption { acp::PermissionOption {
id: allow_option_id.clone(), id: acp::PermissionOptionId(ALWAYS_ALLOW.into()),
name: "Always Allow".into(),
kind: acp::PermissionOptionKind::AllowAlways,
},
acp::PermissionOption {
id: acp::PermissionOptionId(ALLOW.into()),
name: "Allow".into(), name: "Allow".into(),
kind: acp::PermissionOptionKind::AllowOnce, kind: acp::PermissionOptionKind::AllowOnce,
}, },
acp::PermissionOption { acp::PermissionOption {
id: reject_option_id.clone(), id: acp::PermissionOptionId(REJECT.into()),
name: "Reject".into(), name: "Reject".into(),
kind: acp::PermissionOptionKind::RejectOnce, kind: acp::PermissionOptionKind::RejectOnce,
}, },
@ -172,16 +205,33 @@ impl McpServerTool for PermissionTool {
})?? })??
.await?; .await?;
let response = if chosen_option == allow_option_id { let response = match chosen_option.0.as_ref() {
PermissionToolResponse { ALWAYS_ALLOW => {
cx.update(|cx| {
update_settings_file::<AgentSettings>(self.fs.clone(), cx, |settings, _| {
settings.set_always_allow_tool_actions(true);
});
})?;
PermissionToolResponse {
behavior: PermissionToolBehavior::Allow,
updated_input: input.input,
}
}
ALLOW => PermissionToolResponse {
behavior: PermissionToolBehavior::Allow, behavior: PermissionToolBehavior::Allow,
updated_input: input.input, updated_input: input.input,
} },
} else { REJECT => PermissionToolResponse {
debug_assert_eq!(chosen_option, reject_option_id);
PermissionToolResponse {
behavior: PermissionToolBehavior::Deny, behavior: PermissionToolBehavior::Deny,
updated_input: input.input, updated_input: input.input,
},
opt => {
debug_panic!("Unexpected option: {}", opt);
PermissionToolResponse {
behavior: PermissionToolBehavior::Deny,
updated_input: input.input,
}
} }
}; };