acp: Enable ACP additional directories (#57497)

If the agent server supports it, we pass along the multiple directories.

Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Closes #54480

Release Notes:

- acp: External agents that support it now have access to all working
directories in a project.
This commit is contained in:
Ben Brandt 2026-05-22 15:56:45 +02:00 committed by GitHub
parent 91531fad6d
commit dee596fa96
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 23 additions and 114 deletions

View file

@ -116,7 +116,7 @@ pub trait AgentConnection {
}
/// Whether this agent supports additional session directories.
fn supports_session_additional_directories(&self, _cx: &App) -> bool {
fn supports_session_additional_directories(&self) -> bool {
false
}
@ -878,7 +878,7 @@ mod test_support {
self.supports_load_session
}
fn supports_session_additional_directories(&self, _cx: &App) -> bool {
fn supports_session_additional_directories(&self) -> bool {
self.supports_session_additional_directories
}

View file

@ -509,7 +509,6 @@ impl AgentSessionList for AcpSessionList {
cx: &mut App,
) -> Task<Result<AgentSessionListResponse>> {
let conn = self.connection.clone();
let include_additional_directories = cx.has_flag::<AcpBetaFeatureFlag>();
cx.foreground_executor().spawn(async move {
let acp_request = acp::ListSessionsRequest::new()
.cwd(request.cwd)
@ -525,11 +524,7 @@ impl AgentSessionList for AcpSessionList {
session_id: s.session_id,
work_dirs: Some(work_dirs_from_session_info(
s.cwd,
if include_additional_directories {
s.additional_directories
} else {
vec![]
},
s.additional_directories,
)),
title: s.title.map(Into::into),
updated_at: s.updated_at.and_then(|date_str| {
@ -1064,9 +1059,8 @@ impl AcpConnection {
fn session_directories_from_work_dirs(
&self,
work_dirs: &PathList,
cx: &App,
) -> Result<SessionDirectories> {
let supports_additional_directories = self.supports_session_additional_directories(cx);
let supports_additional_directories = self.supports_session_additional_directories();
session_directories_from_work_dirs(work_dirs, supports_additional_directories)
}
@ -1106,7 +1100,7 @@ impl AcpConnection {
}
}
let directories = match self.session_directories_from_work_dirs(&work_dirs, cx) {
let directories = match self.session_directories_from_work_dirs(&work_dirs) {
Ok(directories) => directories,
Err(error) => return Task::ready(Err(error)),
};
@ -1475,7 +1469,7 @@ impl AgentConnection for AcpConnection {
work_dirs: PathList,
cx: &mut App,
) -> Task<Result<Entity<AcpThread>>> {
let directories = match self.session_directories_from_work_dirs(&work_dirs, cx) {
let directories = match self.session_directories_from_work_dirs(&work_dirs) {
Ok(directories) => directories,
Err(error) => return Task::ready(Err(error)),
};
@ -1641,13 +1635,11 @@ impl AgentConnection for AcpConnection {
.is_some()
}
fn supports_session_additional_directories(&self, cx: &App) -> bool {
cx.has_flag::<AcpBetaFeatureFlag>()
&& self
.agent_capabilities
.session_capabilities
.additional_directories
.is_some()
fn supports_session_additional_directories(&self) -> bool {
self.agent_capabilities
.session_capabilities
.additional_directories
.is_some()
}
fn load_session(
@ -2201,8 +2193,8 @@ pub mod test_support {
self.inner.supports_resume_session()
}
fn supports_session_additional_directories(&self, cx: &App) -> bool {
self.inner.supports_session_additional_directories(cx)
fn supports_session_additional_directories(&self) -> bool {
self.inner.supports_session_additional_directories()
}
fn resume_session(
@ -2768,10 +2760,9 @@ mod tests {
}
#[gpui::test]
async fn session_list_includes_additional_directories_in_work_dirs_when_beta_enabled(
async fn session_list_includes_additional_directories_in_work_dirs(
cx: &mut gpui::TestAppContext,
) {
cx.update(|cx| set_acp_beta_override(cx, "on"));
let connection = connect_session_list_test_agent(
vec![
acp::SessionInfo::new("session-1", "/workspace-b").additional_directories(vec![
@ -2809,43 +2800,6 @@ mod tests {
);
}
#[gpui::test]
async fn session_list_excludes_additional_directories_in_work_dirs_when_beta_disabled(
cx: &mut gpui::TestAppContext,
) {
cx.update(|cx| set_acp_beta_override(cx, "off"));
let connection = connect_session_list_test_agent(
vec![
acp::SessionInfo::new("session-1", "/workspace-b").additional_directories(vec![
std::path::PathBuf::from("/workspace-a"),
std::path::PathBuf::from("/workspace-c"),
]),
],
cx,
)
.await;
let session_list = AcpSessionList::new(connection, false);
let response = cx
.update(|cx| session_list.list_sessions(AgentSessionListRequest::default(), cx))
.await
.expect("session list should load");
let session = response
.sessions
.first()
.expect("session list should include the returned session");
let work_dirs = session
.work_dirs
.as_ref()
.expect("session should include work dirs");
assert_eq!(
work_dirs.ordered_paths().cloned().collect::<Vec<_>>(),
vec![std::path::PathBuf::from("/workspace-b")]
);
}
fn set_acp_beta_override(cx: &mut App, value: &str) {
let store = settings::SettingsStore::test(cx);
cx.set_global(store);
@ -2905,16 +2859,12 @@ mod tests {
}
#[gpui::test]
async fn additional_directories_support_requires_beta_flag_and_agent_capability(
async fn additional_directories_support_respects_agent_capability(
cx: &mut gpui::TestAppContext,
) {
cx.update(|cx| {
let store = settings::SettingsStore::test(cx);
cx.set_global(store);
settings::SettingsStore::update_global(cx, |store, _| {
store.register_setting::<feature_flags::FeatureFlagsSettings>();
});
feature_flags::FeatureFlagStore::init(cx);
});
let fs = fs::FakeFs::new(cx.executor());
@ -2922,24 +2872,15 @@ mod tests {
.await;
let project = project::Project::test(fs, [std::path::Path::new("/a")], cx).await;
let mut harness = test_support::connect_fake_acp_connection(project, cx).await;
cx.update(|cx| {
settings::SettingsStore::update_global(cx, |store, _| {
store.register_setting::<feature_flags::FeatureFlagsSettings>();
});
feature_flags::FeatureFlagStore::init(cx);
});
let work_dirs = PathList::new(&[
std::path::PathBuf::from("/workspace-b"),
std::path::PathBuf::from("/workspace-a"),
]);
let missing_capability = cx
.update(|cx| {
harness
.connection
.session_directories_from_work_dirs(&work_dirs, cx)
})
let missing_capability = harness
.connection
.session_directories_from_work_dirs(&work_dirs)
.expect("work dirs should convert");
assert!(missing_capability.additional_directories.is_empty());
@ -2949,44 +2890,12 @@ mod tests {
.session_capabilities
.additional_directories = Some(acp::SessionAdditionalDirectoriesCapabilities::new());
cx.update(|cx| {
settings::SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |content| {
content
.feature_flags
.get_or_insert_default()
.insert("acp-beta".to_string(), "off".to_string());
});
});
});
let disabled = cx
.update(|cx| {
harness
.connection
.session_directories_from_work_dirs(&work_dirs, cx)
})
.expect("work dirs should convert");
assert!(disabled.additional_directories.is_empty());
cx.update(|cx| {
settings::SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |content| {
content
.feature_flags
.get_or_insert_default()
.insert("acp-beta".to_string(), "on".to_string());
});
});
});
let enabled = cx
.update(|cx| {
harness
.connection
.session_directories_from_work_dirs(&work_dirs, cx)
})
let supported = harness
.connection
.session_directories_from_work_dirs(&work_dirs)
.expect("work dirs should convert");
assert_eq!(
enabled,
supported,
SessionDirectories {
cwd: std::path::PathBuf::from("/workspace-b"),
additional_directories: vec![std::path::PathBuf::from("/workspace-a")],

View file

@ -8884,7 +8884,7 @@ impl ThreadView {
.thread
.read(cx)
.connection()
.supports_session_additional_directories(cx)
.supports_session_additional_directories()
{
return None;
}