From de81d610992e25918b07aebb2a49500107c76b64 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:33:54 -0300 Subject: [PATCH 1/8] Fix empty path being shown as an item in the recent projects picker (#53400) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A couple of restore project group/workspaces functions weren't filtering out project groups with empty paths, leading to an empty being shown in the recent projects picker. This is the problem this PR solves: Screenshot 2026-04-08 at 11  06@2x Release Notes: - N/A --- crates/workspace/src/multi_workspace.rs | 3 +++ crates/workspace/src/workspace.rs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/crates/workspace/src/multi_workspace.rs b/crates/workspace/src/multi_workspace.rs index aad5207e8b8d119ed6381a59665782c2b1e540d1..fa4f29ecad1ef37852b779837c58e4925840bf23 100644 --- a/crates/workspace/src/multi_workspace.rs +++ b/crates/workspace/src/multi_workspace.rs @@ -586,6 +586,9 @@ impl MultiWorkspace { pub fn restore_project_group_keys(&mut self, keys: Vec) { let mut restored: Vec = Vec::with_capacity(keys.len()); for key in keys { + if key.path_list().paths().is_empty() { + continue; + } if !restored.contains(&key) { restored.push(key); } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 3d1f67bf69fb818e6155782fdb4779365098cb20..e6244f63b8b45e39b61b2a1c3f4cf2ff27c8609a 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -8778,6 +8778,9 @@ pub async fn restore_multiworkspace( // stale keys from previous sessions get normalized and deduped. let mut resolved_keys: Vec = Vec::new(); for key in project_group_keys.into_iter().map(ProjectGroupKey::from) { + if key.path_list().paths().is_empty() { + continue; + } let mut resolved_paths = Vec::new(); for path in key.path_list().paths() { if let Some(common_dir) = From a706e93ff8e7b87e4a442c96ebf9a4af8413fcda Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:34:34 -0300 Subject: [PATCH 2/8] Update parallel agents onboarding copy (#53423) Release Notes: - N/A --- crates/ai_onboarding/src/ai_onboarding.rs | 14 +++++++++----- crates/auto_update_ui/src/auto_update_ui.rs | 6 +++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/crates/ai_onboarding/src/ai_onboarding.rs b/crates/ai_onboarding/src/ai_onboarding.rs index f51389b8ca25b22798fc1ae9dc2ceee81d60110a..23aa4cc4e8bf2254385b828c952868a5e7bf457a 100644 --- a/crates/ai_onboarding/src/ai_onboarding.rs +++ b/crates/ai_onboarding/src/ai_onboarding.rs @@ -453,7 +453,7 @@ pub struct AgentLayoutOnboarding { impl Render for AgentLayoutOnboarding { fn render(&mut self, _window: &mut ui::Window, _cx: &mut Context) -> impl IntoElement { - let description = "The new threads sidebar, positioned in the far left of your workspace, allows you to manage agents across many projects. Your agent thread lives alongside it, and all other panels live on the right."; + let description = "With the new Threads Sidebar, you can manage multiple agents across several projects, all in one window."; let dismiss_button = div().absolute().top_1().right_1().child( IconButton::new("dismiss", IconName::Close) @@ -500,14 +500,18 @@ impl Render for AgentLayoutOnboarding { .w_full() .relative() .gap_1() - .child(Label::new("A new workspace layout for agentic work")) + .child(Label::new("A new workspace layout for agentic workflows")) .child(Label::new(description).color(Color::Muted).mb_2()) .child( List::new() - .child(ListBulletItem::new("Use your favorite agents in parallel")) - .child(ListBulletItem::new("Isolate agents using worktrees")) .child(ListBulletItem::new( - "Combine multiple projects in one window", + "The Sidebar and Agent Panel are on the left by default", + )) + .child(ListBulletItem::new( + "The Project Panel and all other panels shift to the right", + )) + .child(ListBulletItem::new( + "You can always customize your workspace layout in your Settings", )), ) .child( diff --git a/crates/auto_update_ui/src/auto_update_ui.rs b/crates/auto_update_ui/src/auto_update_ui.rs index 4ff5df72bab2539218f546444be015d63fa97712..26f106c5d7e282cdef733f543e8c9f4d842ced02 100644 --- a/crates/auto_update_ui/src/auto_update_ui.rs +++ b/crates/auto_update_ui/src/auto_update_ui.rs @@ -200,9 +200,9 @@ fn announcement_for_version(version: &Version, cx: &App) -> Option Date: Wed, 8 Apr 2026 18:54:51 +0100 Subject: [PATCH 3/8] Bump Zed to v0.233 (#53404) Release Notes: - N/A --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c6225699f1c882839624cc493e5c130a2cf4c647..e35fd6d1d97f103023e2400573720a066a62b4a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22138,7 +22138,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.232.0" +version = "0.233.0" dependencies = [ "acp_thread", "acp_tools", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 9feaa59c9762208e4e4e85748f21a7a3e0afc3db..915748f50bf75c63ff8f658581d2f247e2d2e44b 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -2,7 +2,7 @@ description = "The fast, collaborative code editor." edition.workspace = true name = "zed" -version = "0.232.0" +version = "0.233.0" publish.workspace = true license = "GPL-3.0-or-later" authors = ["Zed Team "] From 64b93202f84373636494b7ec1d64cb998709b8bb Mon Sep 17 00:00:00 2001 From: "Nitin K. M." <70827815+NewtonChutney@users.noreply.github.com> Date: Wed, 8 Apr 2026 23:50:02 +0530 Subject: [PATCH 4/8] Removal of mold/wild scripts and mentions in docs (#53078) Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [ ] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [ ] Tests cover the new/changed behavior - [ ] Performance impact has been considered and is acceptable Release Notes: - N/A --------- Co-authored-by: Claude Opus 4.6 --- .github/actions/build_docs/action.yml | 4 --- Dockerfile-distros | 3 +- crates/remote/src/transport.rs | 4 --- docs/src/development/linux.md | 27 ---------------- script/install-mold | 45 --------------------------- script/install-wild | 44 -------------------------- script/linux | 14 ++------- 7 files changed, 4 insertions(+), 137 deletions(-) delete mode 100755 script/install-mold delete mode 100755 script/install-wild diff --git a/.github/actions/build_docs/action.yml b/.github/actions/build_docs/action.yml index 1ff271f73ff6b800ec3a94615f31c35a7729bb47..002f6f4653f894cd1f6770f1c7b383a3df13fead 100644 --- a/.github/actions/build_docs/action.yml +++ b/.github/actions/build_docs/action.yml @@ -19,10 +19,6 @@ runs: shell: bash -euxo pipefail {0} run: ./script/linux - - name: Install mold linker - shell: bash -euxo pipefail {0} - run: ./script/install-mold - - name: Download WASI SDK shell: bash -euxo pipefail {0} run: ./script/download-wasi-sdk diff --git a/Dockerfile-distros b/Dockerfile-distros index c8a98d2f7db9bd5a235831257579968ed6d23fe8..5abda933a5a6801e4599df450800f8a92cc4ce60 100644 --- a/Dockerfile-distros +++ b/Dockerfile-distros @@ -11,8 +11,7 @@ ENV CARGO_TERM_COLOR=always COPY script/linux script/ RUN ./script/linux -COPY script/install-mold script/install-cmake script/ -RUN ./script/install-mold "2.34.0" +COPY script/install-cmake script/ RUN ./script/install-cmake "3.30.4" COPY . . diff --git a/crates/remote/src/transport.rs b/crates/remote/src/transport.rs index 8d0f212cfc4f9544d0a827a41aefc3a8af07ee72..5d57b703287539cff4384cec2cbe5966434717c5 100644 --- a/crates/remote/src/transport.rs +++ b/crates/remote/src/transport.rs @@ -263,10 +263,6 @@ async fn build_remote_server_from_source( rust_flags.push_str(&format!(" -C link-arg=-L{path}")); } } - if build_remote_server.contains("mold") { - rust_flags.push_str(" -C link-arg=-fuse-ld=mold"); - } - if platform.arch.as_str() == std::env::consts::ARCH && platform.os.as_str() == std::env::consts::OS { diff --git a/docs/src/development/linux.md b/docs/src/development/linux.md index af81d6220c7122d830cea61461169cd830dc299e..56545111fd11baeb5f272c0439f37f90c104c4d2 100644 --- a/docs/src/development/linux.md +++ b/docs/src/development/linux.md @@ -21,33 +21,6 @@ Clone the [Zed repository](https://github.com/zed-industries/zed). If you prefer to install the system libraries manually, you can find the list of required packages in the `script/linux` file. -### Linkers {#linker} - -On Linux, Rust's default linker is [LLVM's `lld`](https://blog.rust-lang.org/2025/09/18/Rust-1.90.0/). Alternative linkers, especially [Wild](https://github.com/davidlattimore/wild) and [Mold](https://github.com/rui314/mold), can improve clean and incremental build times. - -Zed currently uses Mold in CI because it is more mature. For local development, Wild is recommended because it is typically 5-20% faster than Mold. - -These linkers can be installed with `script/install-mold` and `script/install-wild`. - -To use Wild as your default, add these lines to your `~/.cargo/config.toml`: - -```toml -[target.x86_64-unknown-linux-gnu] -linker = "clang" -rustflags = ["-C", "link-arg=--ld-path=wild"] - -[target.aarch64-unknown-linux-gnu] -linker = "clang" -rustflags = ["-C", "link-arg=--ld-path=wild"] -``` - -To use Mold as your default: - -```toml -[target.'cfg(target_os = "linux")'] -rustflags = ["-C", "link-arg=-fuse-ld=mold"] -``` - ## Building from source Once the dependencies are installed, you can build Zed using [Cargo](https://doc.rust-lang.org/cargo/). diff --git a/script/install-mold b/script/install-mold deleted file mode 100755 index b0bf8517700beb2226d6f06e71fa8d4823175653..0000000000000000000000000000000000000000 --- a/script/install-mold +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash - -# Install `mold` official binaries from GitHub Releases. -# -# Adapted from the official rui314/setup-mold@v1 action to: -# * use environment variables instead of action inputs -# * remove make-default support -# * use curl instead of wget -# * support doas for sudo -# * support redhat systems -# See: https://github.com/rui314/setup-mold/blob/main/action.yml - -set -euo pipefail - -MOLD_VERSION="2.34.0" - -if [ "$(uname -s)" != "Linux" ]; then - echo "Error: This script is intended for Linux systems only." - exit 1 -elif [ -e /usr/local/bin/mold ]; then - echo "Warning: existing mold found at /usr/local/bin/mold. Skipping installation." - exit 0 -fi - -if [ "$(whoami)" = root ]; then SUDO=; else SUDO="$(command -v sudo || command -v doas || true)"; fi - -MOLD_REPO="${MOLD_REPO:-https://github.com/rui314/mold}" -MOLD_URL="${MOLD_URL:-$MOLD_REPO}/releases/download/v$MOLD_VERSION/mold-$MOLD_VERSION-$(uname -m)-linux.tar.gz" - -echo "Downloading from $MOLD_URL" -curl -fsSL --output - "$MOLD_URL" \ - | $SUDO tar -C /usr/local --strip-components=1 --no-overwrite-dir -xzf - - -# Note this binary depends on the system libatomic.so.1 which is usually -# provided as a dependency of gcc so it should be available on most systems. - -cat </dev/null 2>&1 && wild --version | grep -Fq "$WILD_VERSION" ; then - echo "Warning: existing wild $WILD_VERSION found at $(command -v wild). Skipping installation." - exit 0 -fi - -if [ "$(whoami)" = root ]; then SUDO=; else SUDO="$(command -v sudo || command -v doas || true)"; fi - -ARCH="$(uname -m)" -WILD_REPO="${WILD_REPO:-https://github.com/davidlattimore/wild}" -WILD_PACKAGE="wild-linker-${WILD_VERSION}-${ARCH}-unknown-linux-gnu" -WILD_URL="${WILD_URL:-$WILD_REPO}/releases/download/$WILD_VERSION/${WILD_PACKAGE}.tar.gz" -DEST_DIR=/usr/local/bin - -echo "Downloading from $WILD_URL" -curl -fsSL --output - "$WILD_URL" \ - | $SUDO tar -C ${DEST_DIR} --strip-components=1 --no-overwrite-dir -xzf - \ - "${WILD_PACKAGE}/wild" - -cat < /dev/null 2>&1 || curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y cat < Option { + ) -> Option>> { None } diff --git a/crates/agent_servers/Cargo.toml b/crates/agent_servers/Cargo.toml index 7151f0084b1cb7d9b206f57551ce715ef67483f7..5fbf1e821cb4a41f09c433ec05fdde9fbbde1a9f 100644 --- a/crates/agent_servers/Cargo.toml +++ b/crates/agent_servers/Cargo.toml @@ -39,6 +39,7 @@ language_model.workspace = true log.workspace = true project.workspace = true release_channel.workspace = true +remote.workspace = true reqwest_client = { workspace = true, optional = true } serde.workspace = true serde_json.workspace = true diff --git a/crates/agent_servers/src/acp.rs b/crates/agent_servers/src/acp.rs index e56db9df927ab3cdf838587f1cb4f9514eb5a758..dbcaabed1cf1971a6e281d8d31f8dad25dfb7434 100644 --- a/crates/agent_servers/src/acp.rs +++ b/crates/agent_servers/src/acp.rs @@ -10,20 +10,20 @@ use collections::HashMap; use feature_flags::{AcpBetaFeatureFlag, FeatureFlagAppExt as _}; use futures::AsyncBufReadExt as _; use futures::io::BufReader; -use project::agent_server_store::AgentServerCommand; +use project::agent_server_store::{AgentServerCommand, AgentServerStore}; use project::{AgentId, Project}; +use remote::remote_client::Interactive; use serde::Deserialize; use settings::Settings as _; -use task::{ShellBuilder, SpawnInTerminal}; -use util::ResultExt as _; -use util::path_list::PathList; -use util::process::Child; - use std::path::PathBuf; use std::process::Stdio; use std::rc::Rc; use std::{any::Any, cell::RefCell}; +use task::{ShellBuilder, SpawnInTerminal}; use thiserror::Error; +use util::ResultExt as _; +use util::path_list::PathList; +use util::process::Child; use anyhow::{Context as _, Result}; use gpui::{App, AppContext as _, AsyncApp, Entity, SharedString, Task, WeakEntity}; @@ -46,7 +46,7 @@ pub struct AcpConnection { connection: Rc, sessions: Rc>>, auth_methods: Vec, - command: AgentServerCommand, + agent_server_store: WeakEntity, agent_capabilities: acp::AgentCapabilities, default_mode: Option, default_model: Option, @@ -167,6 +167,7 @@ pub async fn connect( agent_id: AgentId, project: Entity, command: AgentServerCommand, + agent_server_store: WeakEntity, default_mode: Option, default_model: Option, default_config_options: HashMap, @@ -176,6 +177,7 @@ pub async fn connect( agent_id, project, command.clone(), + agent_server_store, default_mode, default_model, default_config_options, @@ -192,23 +194,52 @@ impl AcpConnection { agent_id: AgentId, project: Entity, command: AgentServerCommand, + agent_server_store: WeakEntity, default_mode: Option, default_model: Option, default_config_options: HashMap, cx: &mut AsyncApp, ) -> Result { + let root_dir = project.read_with(cx, |project, cx| { + project + .default_path_list(cx) + .ordered_paths() + .next() + .cloned() + }); + let original_command = command.clone(); + let (path, args, env) = project + .read_with(cx, |project, cx| { + project.remote_client().and_then(|client| { + let template = client + .read(cx) + .build_command_with_options( + Some(command.path.display().to_string()), + &command.args, + &command.env.clone().into_iter().flatten().collect(), + root_dir.as_ref().map(|path| path.display().to_string()), + None, + Interactive::No, + ) + .log_err()?; + Some((template.program, template.args, template.env)) + }) + }) + .unwrap_or_else(|| { + ( + command.path.display().to_string(), + command.args, + command.env.unwrap_or_default(), + ) + }); + let shell = cx.update(|cx| TerminalSettings::get(None, cx).shell.clone()); let builder = ShellBuilder::new(&shell, cfg!(windows)).non_interactive(); - let mut child = - builder.build_std_command(Some(command.path.display().to_string()), &command.args); - child.envs(command.env.iter().flatten()); - if let Some(cwd) = project.update(cx, |project, cx| { + let mut child = builder.build_std_command(Some(path.clone()), &args); + child.envs(env.clone()); + if let Some(cwd) = project.read_with(cx, |project, _cx| { if project.is_local() { - project - .default_path_list(cx) - .ordered_paths() - .next() - .cloned() + root_dir.as_ref() } else { None } @@ -220,11 +251,7 @@ impl AcpConnection { let stdout = child.stdout.take().context("Failed to take stdout")?; let stdin = child.stdin.take().context("Failed to take stdin")?; let stderr = child.stderr.take().context("Failed to take stderr")?; - log::debug!( - "Spawning external agent server: {:?}, {:?}", - command.path, - command.args - ); + log::debug!("Spawning external agent server: {:?}, {:?}", path, args); log::trace!("Spawned (pid: {})", child.id()); let sessions = Rc::new(RefCell::new(HashMap::default())); @@ -342,13 +369,13 @@ impl AcpConnection { // TODO: Remove this override once Google team releases their official auth methods let auth_methods = if agent_id.0.as_ref() == GEMINI_ID { - let mut args = command.args.clone(); - args.retain(|a| a != "--experimental-acp" && a != "--acp"); + let mut gemini_args = original_command.args.clone(); + gemini_args.retain(|a| a != "--experimental-acp" && a != "--acp"); let value = serde_json::json!({ "label": "gemini /auth", - "command": command.path.to_string_lossy().into_owned(), - "args": args, - "env": command.env.clone().unwrap_or_default(), + "command": original_command.path.to_string_lossy(), + "args": gemini_args, + "env": original_command.env.unwrap_or_default(), }); let meta = acp::Meta::from_iter([("terminal-auth".to_string(), value)]); vec![acp::AuthMethod::Agent( @@ -362,7 +389,7 @@ impl AcpConnection { Ok(Self { id: agent_id, auth_methods, - command, + agent_server_store, connection, telemetry_id, sessions, @@ -494,18 +521,12 @@ fn terminal_auth_task( agent_id: &AgentId, method: &acp::AuthMethodTerminal, ) -> SpawnInTerminal { - let mut args = command.args.clone(); - args.extend(method.args.clone()); - - let mut env = command.env.clone().unwrap_or_default(); - env.extend(method.env.clone()); - acp_thread::build_terminal_auth_task( terminal_auth_task_id(agent_id, &method.id), method.name.clone(), command.path.to_string_lossy().into_owned(), - args, - env, + command.args.clone(), + command.env.clone().unwrap_or_default(), ) } @@ -890,7 +911,7 @@ impl AgentConnection for AcpConnection { &self, method_id: &acp::AuthMethodId, cx: &App, - ) -> Option { + ) -> Option>> { let method = self .auth_methods .iter() @@ -898,9 +919,28 @@ impl AgentConnection for AcpConnection { match method { acp::AuthMethod::Terminal(terminal) if cx.has_flag::() => { - Some(terminal_auth_task(&self.command, &self.id, terminal)) + let agent_id = self.id.clone(); + let terminal = terminal.clone(); + let store = self.agent_server_store.clone(); + Some(cx.spawn(async move |cx| { + let command = store + .update(cx, |store, cx| { + let agent = store + .get_external_agent(&agent_id) + .context("Agent server not found")?; + anyhow::Ok(agent.get_command( + terminal.args.clone(), + HashMap::from_iter(terminal.env.clone()), + &mut cx.to_async(), + )) + })? + .context("Failed to get agent command")? + .await?; + Ok(terminal_auth_task(&command, &agent_id, &terminal)) + })) } - _ => meta_terminal_auth_task(&self.id, method_id, method), + _ => meta_terminal_auth_task(&self.id, method_id, method) + .map(|task| Task::ready(Ok(task))), } } @@ -1075,39 +1115,32 @@ mod tests { use super::*; #[test] - fn terminal_auth_task_reuses_command_and_merges_args_and_env() { + fn terminal_auth_task_builds_spawn_from_prebuilt_command() { let command = AgentServerCommand { path: "/path/to/agent".into(), - args: vec!["--acp".into(), "--verbose".into()], + args: vec!["--acp".into(), "--verbose".into(), "/auth".into()], env: Some(HashMap::from_iter([ ("BASE".into(), "1".into()), - ("SHARED".into(), "base".into()), + ("SHARED".into(), "override".into()), + ("EXTRA".into(), "2".into()), ])), }; - let method = acp::AuthMethodTerminal::new("login", "Login") - .args(vec!["/auth".into()]) - .env(std::collections::HashMap::from_iter([ - ("EXTRA".into(), "2".into()), - ("SHARED".into(), "override".into()), - ])); + let method = acp::AuthMethodTerminal::new("login", "Login"); - let terminal_auth_task = terminal_auth_task(&command, &AgentId::new("test-agent"), &method); + let task = terminal_auth_task(&command, &AgentId::new("test-agent"), &method); + assert_eq!(task.command.as_deref(), Some("/path/to/agent")); + assert_eq!(task.args, vec!["--acp", "--verbose", "/auth"]); assert_eq!( - terminal_auth_task.command.as_deref(), - Some("/path/to/agent") - ); - assert_eq!(terminal_auth_task.args, vec!["--acp", "--verbose", "/auth"]); - assert_eq!( - terminal_auth_task.env, + task.env, HashMap::from_iter([ ("BASE".into(), "1".into()), ("SHARED".into(), "override".into()), ("EXTRA".into(), "2".into()), ]) ); - assert_eq!(terminal_auth_task.label, "Login"); - assert_eq!(terminal_auth_task.command_label, "Login"); + assert_eq!(task.label, "Login"); + assert_eq!(task.command_label, "Login"); } #[test] @@ -1127,21 +1160,17 @@ mod tests { )])), ); - let terminal_auth_task = - meta_terminal_auth_task(&AgentId::new("test-agent"), &method_id, &method) - .expect("expected legacy terminal auth task"); + let task = meta_terminal_auth_task(&AgentId::new("test-agent"), &method_id, &method) + .expect("expected legacy terminal auth task"); + assert_eq!(task.id.0, "external-agent-test-agent-legacy-login-login"); + assert_eq!(task.command.as_deref(), Some("legacy-agent")); + assert_eq!(task.args, vec!["auth", "--interactive"]); assert_eq!( - terminal_auth_task.id.0, - "external-agent-test-agent-legacy-login-login" - ); - assert_eq!(terminal_auth_task.command.as_deref(), Some("legacy-agent")); - assert_eq!(terminal_auth_task.args, vec!["auth", "--interactive"]); - assert_eq!( - terminal_auth_task.env, + task.env, HashMap::from_iter([("AUTH_MODE".into(), "interactive".into())]) ); - assert_eq!(terminal_auth_task.label, "legacy /auth"); + assert_eq!(task.label, "legacy /auth"); } #[test] @@ -1186,30 +1215,30 @@ mod tests { let command = AgentServerCommand { path: "/path/to/agent".into(), - args: vec!["--acp".into()], - env: Some(HashMap::from_iter([("BASE".into(), "1".into())])), + args: vec!["--acp".into(), "/auth".into()], + env: Some(HashMap::from_iter([ + ("BASE".into(), "1".into()), + ("AUTH_MODE".into(), "first-class".into()), + ])), }; - let terminal_auth_task = match &method { + let task = match &method { acp::AuthMethod::Terminal(terminal) => { terminal_auth_task(&command, &AgentId::new("test-agent"), terminal) } _ => unreachable!(), }; + assert_eq!(task.command.as_deref(), Some("/path/to/agent")); + assert_eq!(task.args, vec!["--acp", "/auth"]); assert_eq!( - terminal_auth_task.command.as_deref(), - Some("/path/to/agent") - ); - assert_eq!(terminal_auth_task.args, vec!["--acp", "/auth"]); - assert_eq!( - terminal_auth_task.env, + task.env, HashMap::from_iter([ ("BASE".into(), "1".into()), ("AUTH_MODE".into(), "first-class".into()), ]) ); - assert_eq!(terminal_auth_task.label, "Login"); + assert_eq!(task.label, "Login"); } } diff --git a/crates/agent_servers/src/custom.rs b/crates/agent_servers/src/custom.rs index fb8d0a515244576d2cf02e4989cbd71beca448c7..151ddcefcfb0b839199c21d826a4c9f6836f876b 100644 --- a/crates/agent_servers/src/custom.rs +++ b/crates/agent_servers/src/custom.rs @@ -360,17 +360,17 @@ impl AgentServer for CustomAgentServer { let agent = store.get_external_agent(&agent_id).with_context(|| { format!("Custom agent server `{}` is not registered", agent_id) })?; - anyhow::Ok(agent.get_command( - extra_env, - delegate.new_version_available, - &mut cx.to_async(), - )) + if let Some(new_version_available_tx) = delegate.new_version_available { + agent.set_new_version_available_tx(new_version_available_tx); + } + anyhow::Ok(agent.get_command(vec![], extra_env, &mut cx.to_async())) })?? .await?; let connection = crate::acp::connect( agent_id, project, command, + store.clone(), default_mode, default_model, default_config_options, diff --git a/crates/agent_ui/src/conversation_view.rs b/crates/agent_ui/src/conversation_view.rs index 80190858151b2cf79500290a95ee0d0b6a4e8c97..1bad3c55646f9e912f79210db4afcde89c00e68a 100644 --- a/crates/agent_ui/src/conversation_view.rs +++ b/crates/agent_ui/src/conversation_view.rs @@ -1510,24 +1510,30 @@ impl ConversationView { let agent_telemetry_id = connection.telemetry_id(); - if let Some(login) = connection.terminal_auth_task(&method, cx) { + if let Some(login_task) = connection.terminal_auth_task(&method, cx) { configuration_view.take(); pending_auth_method.replace(method.clone()); let project = self.project.clone(); - let authenticate = Self::spawn_external_agent_login( - login, - workspace, - project, - method.clone(), - false, - window, - cx, - ); cx.notify(); self.auth_task = Some(cx.spawn_in(window, { async move |this, cx| { - let result = authenticate.await; + let result = async { + let login = login_task.await?; + this.update_in(cx, |_this, window, cx| { + Self::spawn_external_agent_login( + login, + workspace, + project, + method.clone(), + false, + window, + cx, + ) + })? + .await + } + .await; match &result { Ok(_) => telemetry::event!( diff --git a/crates/project/src/agent_server_store.rs b/crates/project/src/agent_server_store.rs index 0b6bb2b739f677ca1f4f3d5558538372ec6e86ff..5a9721d827cf3d189c7954f0698b662e5aaf4852 100644 --- a/crates/project/src/agent_server_store.rs +++ b/crates/project/src/agent_server_store.rs @@ -1,4 +1,3 @@ -use remote::Interactive; use std::{ any::Any, path::{Path, PathBuf}, @@ -116,9 +115,9 @@ pub enum ExternalAgentSource { pub trait ExternalAgentServer { fn get_command( - &mut self, + &self, + extra_args: Vec, extra_env: HashMap, - new_version_available_tx: Option>>, cx: &mut AsyncApp, ) -> Task>; @@ -800,11 +799,10 @@ impl AgentServerStore { if no_browser { extra_env.insert("NO_BROWSER".to_owned(), "1".to_owned()); } - anyhow::Ok(agent.get_command( - extra_env, - new_version_available_tx, - &mut cx.to_async(), - )) + if let Some(new_version_available_tx) = new_version_available_tx { + agent.set_new_version_available_tx(new_version_available_tx); + } + anyhow::Ok(agent.get_command(vec![], extra_env, &mut cx.to_async())) })? .await?; Ok(proto::AgentServerCommand { @@ -986,16 +984,15 @@ impl ExternalAgentServer for RemoteExternalAgentServer { } fn get_command( - &mut self, + &self, + extra_args: Vec, extra_env: HashMap, - new_version_available_tx: Option>>, cx: &mut AsyncApp, ) -> Task> { let project_id = self.project_id; let name = self.name.to_string(); let upstream_client = self.upstream_client.downgrade(); let worktree_store = self.worktree_store.clone(); - self.new_version_available_tx = new_version_available_tx; cx.spawn(async move |cx| { let root_dir = worktree_store.read_with(cx, |worktree_store, cx| { crate::Project::default_visible_worktree_paths(worktree_store, cx) @@ -1015,22 +1012,13 @@ impl ExternalAgentServer for RemoteExternalAgentServer { }) })? .await?; - let root_dir = response.root_dir; + response.args.extend(extra_args); response.env.extend(extra_env); - let command = upstream_client.update(cx, |client, _| { - client.build_command_with_options( - Some(response.path), - &response.args, - &response.env.into_iter().collect(), - Some(root_dir.clone()), - None, - Interactive::No, - ) - })??; + Ok(AgentServerCommand { - path: command.program.into(), - args: command.args, - env: Some(command.env), + path: response.path.into(), + args: response.args, + env: Some(response.env.into_iter().collect()), }) }) } @@ -1162,12 +1150,11 @@ impl ExternalAgentServer for LocalExtensionArchiveAgent { } fn get_command( - &mut self, + &self, + extra_args: Vec, extra_env: HashMap, - new_version_available_tx: Option>>, cx: &mut AsyncApp, ) -> Task> { - self.new_version_available_tx = new_version_available_tx; let fs = self.fs.clone(); let http_client = self.http_client.clone(); let node_runtime = self.node_runtime.clone(); @@ -1309,9 +1296,12 @@ impl ExternalAgentServer for LocalExtensionArchiveAgent { } }; + let mut args = target_config.args.clone(); + args.extend(extra_args); + let command = AgentServerCommand { path: cmd_path, - args: target_config.args.clone(), + args, env: Some(env), }; @@ -1354,12 +1344,11 @@ impl ExternalAgentServer for LocalRegistryArchiveAgent { } fn get_command( - &mut self, + &self, + extra_args: Vec, extra_env: HashMap, - new_version_available_tx: Option>>, cx: &mut AsyncApp, ) -> Task> { - self.new_version_available_tx = new_version_available_tx; let fs = self.fs.clone(); let http_client = self.http_client.clone(); let node_runtime = self.node_runtime.clone(); @@ -1486,9 +1475,12 @@ impl ExternalAgentServer for LocalRegistryArchiveAgent { } }; + let mut args = target_config.args.clone(); + args.extend(extra_args); + let command = AgentServerCommand { path: cmd_path, - args: target_config.args.clone(), + args, env: Some(env), }; @@ -1530,12 +1522,11 @@ impl ExternalAgentServer for LocalRegistryNpxAgent { } fn get_command( - &mut self, + &self, + extra_args: Vec, extra_env: HashMap, - new_version_available_tx: Option>>, cx: &mut AsyncApp, ) -> Task> { - self.new_version_available_tx = new_version_available_tx; let node_runtime = self.node_runtime.clone(); let project_environment = self.project_environment.downgrade(); let package = self.package.clone(); @@ -1566,9 +1557,12 @@ impl ExternalAgentServer for LocalRegistryNpxAgent { env.extend(extra_env); env.extend(settings_env); + let mut args = npm_command.args; + args.extend(extra_args); + let command = AgentServerCommand { path: npm_command.path, - args: npm_command.args, + args, env: Some(env), }; @@ -1592,9 +1586,9 @@ struct LocalCustomAgent { impl ExternalAgentServer for LocalCustomAgent { fn get_command( - &mut self, + &self, + extra_args: Vec, extra_env: HashMap, - _new_version_available_tx: Option>>, cx: &mut AsyncApp, ) -> Task> { let mut command = self.command.clone(); @@ -1609,6 +1603,7 @@ impl ExternalAgentServer for LocalCustomAgent { env.extend(command.env.unwrap_or_default()); env.extend(extra_env); command.env = Some(env); + command.args.extend(extra_args); Ok(command) }) } diff --git a/crates/project/tests/integration/ext_agent_tests.rs b/crates/project/tests/integration/ext_agent_tests.rs index bd4acf2b3e9419b62ff676331383b48f98874345..82135485d3f262e5984ddbd003b69b828839d4bc 100644 --- a/crates/project/tests/integration/ext_agent_tests.rs +++ b/crates/project/tests/integration/ext_agent_tests.rs @@ -8,9 +8,9 @@ struct NoopExternalAgent; impl ExternalAgentServer for NoopExternalAgent { fn get_command( - &mut self, + &self, + _extra_args: Vec, _extra_env: HashMap, - _new_version_available_tx: Option>>, _cx: &mut AsyncApp, ) -> Task> { Task::ready(Ok(AgentServerCommand { diff --git a/crates/project/tests/integration/extension_agent_tests.rs b/crates/project/tests/integration/extension_agent_tests.rs index 577bc3b2901c52f4f47d9d0c82ef89fc66e2c21a..5af2cd229c476a261a5d37666e00d2dff3b293b4 100644 --- a/crates/project/tests/integration/extension_agent_tests.rs +++ b/crates/project/tests/integration/extension_agent_tests.rs @@ -24,9 +24,9 @@ struct NoopExternalAgent; impl ExternalAgentServer for NoopExternalAgent { fn get_command( - &mut self, + &self, + _extra_args: Vec, _extra_env: HashMap, - _new_version_available_tx: Option>>, _cx: &mut AsyncApp, ) -> Task> { Task::ready(Ok(AgentServerCommand { diff --git a/crates/remote_server/src/remote_editing_tests.rs b/crates/remote_server/src/remote_editing_tests.rs index f0f23577d31075ab815d6dba1cdbdccd275c184a..571c5e7ea1aa5d623cf70d8fd06252bd0860de1b 100644 --- a/crates/remote_server/src/remote_editing_tests.rs +++ b/crates/remote_server/src/remote_editing_tests.rs @@ -2256,8 +2256,8 @@ async fn test_remote_external_agent_server( .get_external_agent(&"foo".into()) .unwrap() .get_command( + vec![], HashMap::from_iter([("OTHER_VAR".into(), "other-val".into())]), - None, &mut cx.to_async(), ) }) @@ -2267,8 +2267,8 @@ async fn test_remote_external_agent_server( assert_eq!( command, AgentServerCommand { - path: "mock".into(), - args: vec!["foo-cli".into(), "--flag".into()], + path: "foo-cli".into(), + args: vec!["--flag".into()], env: Some(HashMap::from_iter([ ("NO_BROWSER".into(), "1".into()), ("VAR".into(), "val".into()), From dc3d8f178099c964fe775a9a1a508ff1792b78d8 Mon Sep 17 00:00:00 2001 From: Finn Evers Date: Wed, 8 Apr 2026 20:31:35 +0200 Subject: [PATCH 6/8] ci: Remove noisy compliance webhook print (#53432) This is very noisy and does not provide much, the artifact link to the report is sufficient for now. Release Notes: - N/A --- .github/workflows/compliance_check.yml | 7 +------ .github/workflows/release.yml | 14 ++------------ tooling/xtask/src/tasks/workflows/release.rs | 7 +------ 3 files changed, 4 insertions(+), 24 deletions(-) diff --git a/.github/workflows/compliance_check.yml b/.github/workflows/compliance_check.yml index c8bce3302ada3e56564616f145ac660fdc743d20..4425032a44d0516b79685580cdab93e6f673a3db 100644 --- a/.github/workflows/compliance_check.yml +++ b/.github/workflows/compliance_check.yml @@ -52,18 +52,13 @@ jobs: - name: send_compliance_slack_notification if: always() run: | - REPORT_CONTENT="" - if [ -f "target/compliance-report.md" ]; then - REPORT_CONTENT=$(cat "target/compliance-report.md") - fi - if [ "$COMPLIANCE_OUTCOME" == "success" ]; then STATUS="✅ Scheduled compliance check passed for $COMPLIANCE_TAG" else STATUS="⚠️ Scheduled compliance check failed for $COMPLIANCE_TAG" fi - MESSAGE=$(printf "%s\n\nReport: %s\nPRs needing review: %s\n\n%s" "$STATUS" "$ARTIFACT_URL" "https://github.com/zed-industries/zed/pulls?q=is%3Apr+is%3Aclosed+label%3A%22PR+state%3Aneeds+review%22" "$REPORT_CONTENT") + MESSAGE=$(printf "%s\n\nReport: %s\nPRs needing review: %s" "$STATUS" "$ARTIFACT_URL" "https://github.com/zed-industries/zed/pulls?q=is%3Apr+is%3Aclosed+label%3A%22PR+state%3Aneeds+review%22") curl -X POST -H 'Content-type: application/json' \ --data "$(jq -n --arg text "$MESSAGE" '{"text": $text}')" \ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f20f64c7a54151fedcd3b4e36b4dc492578b827d..74cdc3f53f73f9a56d4cf1a3c77b41d3c3093be5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -325,18 +325,13 @@ jobs: - name: send_compliance_slack_notification if: always() run: | - REPORT_CONTENT="" - if [ -f "target/compliance-report.md" ]; then - REPORT_CONTENT=$(cat "target/compliance-report.md") - fi - if [ "$COMPLIANCE_OUTCOME" == "success" ]; then STATUS="✅ Compliance check passed for $COMPLIANCE_TAG" else STATUS="❌ Compliance check failed for $COMPLIANCE_TAG" fi - MESSAGE=$(printf "%s\n\nReport: %s\nPRs needing review: %s\n\n%s" "$STATUS" "$ARTIFACT_URL" "https://github.com/zed-industries/zed/pulls?q=is%3Apr+is%3Aclosed+label%3A%22PR+state%3Aneeds+review%22" "$REPORT_CONTENT") + MESSAGE=$(printf "%s\n\nReport: %s\nPRs needing review: %s" "$STATUS" "$ARTIFACT_URL" "https://github.com/zed-industries/zed/pulls?q=is%3Apr+is%3Aclosed+label%3A%22PR+state%3Aneeds+review%22") curl -X POST -H 'Content-type: application/json' \ --data "$(jq -n --arg text "$MESSAGE" '{"text": $text}')" \ @@ -695,18 +690,13 @@ jobs: - name: send_compliance_slack_notification if: always() run: | - REPORT_CONTENT="" - if [ -f "target/compliance-report.md" ]; then - REPORT_CONTENT=$(cat "target/compliance-report.md") - fi - if [ "$COMPLIANCE_OUTCOME" == "success" ]; then STATUS="✅ Compliance check passed for $COMPLIANCE_TAG" else STATUS="❌ Compliance check failed for $COMPLIANCE_TAG" fi - MESSAGE=$(printf "%s\n\nReport: %s\nPRs needing review: %s\n\n%s" "$STATUS" "$ARTIFACT_URL" "https://github.com/zed-industries/zed/pulls?q=is%3Apr+is%3Aclosed+label%3A%22PR+state%3Aneeds+review%22" "$REPORT_CONTENT") + MESSAGE=$(printf "%s\n\nReport: %s\nPRs needing review: %s" "$STATUS" "$ARTIFACT_URL" "https://github.com/zed-industries/zed/pulls?q=is%3Apr+is%3Aclosed+label%3A%22PR+state%3Aneeds+review%22") curl -X POST -H 'Content-type: application/json' \ --data "$(jq -n --arg text "$MESSAGE" '{"text": $text}')" \ diff --git a/tooling/xtask/src/tasks/workflows/release.rs b/tooling/xtask/src/tasks/workflows/release.rs index 27f78fd74c2652f623197e3e9207dc6b91fa5c56..99e8463a259df2f40b39072321614881a8f56628 100644 --- a/tooling/xtask/src/tasks/workflows/release.rs +++ b/tooling/xtask/src/tasks/workflows/release.rs @@ -177,18 +177,13 @@ pub(crate) fn add_compliance_notification_steps( }; let script = formatdoc! {r#" - REPORT_CONTENT="" - if [ -f "{COMPLIANCE_REPORT_FILE}" ]; then - REPORT_CONTENT=$(cat "{COMPLIANCE_REPORT_FILE}") - fi - if [ "$COMPLIANCE_OUTCOME" == "success" ]; then STATUS="{success_prefix} for $COMPLIANCE_TAG" else STATUS="{failure_prefix} for $COMPLIANCE_TAG" fi - MESSAGE=$(printf "%s\n\nReport: %s\nPRs needing review: %s\n\n%s" "$STATUS" "$ARTIFACT_URL" "{NEEDS_REVIEW_PULLS_URL}" "$REPORT_CONTENT") + MESSAGE=$(printf "%s\n\nReport: %s\nPRs needing review: %s" "$STATUS" "$ARTIFACT_URL" "{NEEDS_REVIEW_PULLS_URL}") curl -X POST -H 'Content-type: application/json' \ --data "$(jq -n --arg text "$MESSAGE" '{{"text": $text}}')" \ From e758d257cfb6e55b97e6a41f0dc84addcf51debe Mon Sep 17 00:00:00 2001 From: Finn Evers Date: Wed, 8 Apr 2026 20:34:13 +0200 Subject: [PATCH 7/8] ci: Fix artifact naming conflict (#53433) This fixes the issue we saw in https://github.com/zed-industries/zed/actions/runs/24147949240 Release Notes: - N/A --- .github/workflows/release.yml | 1 + tooling/xtask/src/tasks/workflows/release.rs | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 74cdc3f53f73f9a56d4cf1a3c77b41d3c3093be5..157ec94fb901e63a2223db5b9d7ca9d3de4280d1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -687,6 +687,7 @@ jobs: name: compliance-report.md path: target/compliance-report.md if-no-files-found: error + overwrite: true - name: send_compliance_slack_notification if: always() run: | diff --git a/tooling/xtask/src/tasks/workflows/release.rs b/tooling/xtask/src/tasks/workflows/release.rs index 99e8463a259df2f40b39072321614881a8f56628..a67bcefa6b9fe83c292339fa2ba9bc972082e266 100644 --- a/tooling/xtask/src/tasks/workflows/release.rs +++ b/tooling/xtask/src/tasks/workflows/release.rs @@ -157,6 +157,7 @@ const NEEDS_REVIEW_PULLS_URL: &str = "https://github.com/zed-industries/zed/pull pub(crate) enum ComplianceContext { Release, + ReleaseNonBlocking, Scheduled { tag_source: StepOutput }, } @@ -165,11 +166,16 @@ pub(crate) fn add_compliance_notification_steps( context: ComplianceContext, compliance_step_id: &str, ) -> gh_workflow::Job { - let upload_step = - upload_artifact(COMPLIANCE_REPORT_FILE).if_condition(Expression::new("always()")); + let upload_step = upload_artifact(COMPLIANCE_REPORT_FILE) + .if_condition(Expression::new("always()")) + .when(matches!(context, ComplianceContext::Release), |step| { + step.add_with(("overwrite", true)) + }); let (success_prefix, failure_prefix) = match context { - ComplianceContext::Release => ("✅ Compliance check passed", "❌ Compliance check failed"), + ComplianceContext::Release | ComplianceContext::ReleaseNonBlocking => { + ("✅ Compliance check passed", "❌ Compliance check failed") + } ComplianceContext::Scheduled { .. } => ( "✅ Scheduled compliance check passed", "⚠️ Scheduled compliance check failed", @@ -202,7 +208,9 @@ pub(crate) fn add_compliance_notification_steps( .add_env(( "COMPLIANCE_TAG", match context { - ComplianceContext::Release => Context::github().ref_name().to_string(), + ComplianceContext::Release | ComplianceContext::ReleaseNonBlocking => { + Context::github().ref_name().to_string() + } ComplianceContext::Scheduled { tag_source } => tag_source.to_string(), }, )) @@ -237,7 +245,7 @@ fn compliance_check() -> NamedJob { named::job(add_compliance_notification_steps( job, - ComplianceContext::Release, + ComplianceContext::ReleaseNonBlocking, "run-compliance-check", )) } From abc2f5f7d6c1e902bbf4496c1bef9915c6877604 Mon Sep 17 00:00:00 2001 From: Finn Evers Date: Wed, 8 Apr 2026 20:35:36 +0200 Subject: [PATCH 8/8] ci: Continue on error if non-blocking compliance step fails (#53398) This will make rerunning failing jobs easier, as this here is just a pager, not anything we need to rerun - there exists a second check that is blocking and should be rerun, this job however does not need to be. Release Notes: - N/A --- .github/workflows/release.yml | 1 + tooling/xtask/src/tasks/workflows/release.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 157ec94fb901e63a2223db5b9d7ca9d3de4280d1..bdd1205bf8cbcbb3afaf88a3342afe5df3cda7bd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -315,6 +315,7 @@ jobs: env: GITHUB_APP_ID: ${{ secrets.ZED_ZIPPY_APP_ID }} GITHUB_APP_KEY: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }} + continue-on-error: true - name: '@actions/upload-artifact compliance-report.md' if: always() uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 diff --git a/tooling/xtask/src/tasks/workflows/release.rs b/tooling/xtask/src/tasks/workflows/release.rs index a67bcefa6b9fe83c292339fa2ba9bc972082e266..ea9542ea56b86bfceb2bd1097b20e9bf93e941f2 100644 --- a/tooling/xtask/src/tasks/workflows/release.rs +++ b/tooling/xtask/src/tasks/workflows/release.rs @@ -231,6 +231,7 @@ fn compliance_check() -> NamedJob { .id("run-compliance-check") .add_env(("GITHUB_APP_ID", vars::ZED_ZIPPY_APP_ID)) .add_env(("GITHUB_APP_KEY", vars::ZED_ZIPPY_APP_PRIVATE_KEY)) + .continue_on_error(true) } let job = release_job(&[])