Merge branch 'main' into fix-artifact-path

MrSubidubi created

Change summary

.github/actions/build_docs/action.yml                     |   4 
.github/workflows/compliance_check.yml                    |   7 
.github/workflows/release.yml                             |  17 
Cargo.lock                                                |   3 
Dockerfile-distros                                        |   3 
crates/acp_thread/src/connection.rs                       |   2 
crates/agent_servers/Cargo.toml                           |   1 
crates/agent_servers/src/acp.rs                           | 183 +++++---
crates/agent_servers/src/custom.rs                        |  10 
crates/agent_ui/src/conversation_view.rs                  |  28 
crates/ai_onboarding/src/ai_onboarding.rs                 |  14 
crates/auto_update_ui/src/auto_update_ui.rs               |   6 
crates/project/src/agent_server_store.rs                  |  73 +-
crates/project/tests/integration/ext_agent_tests.rs       |   4 
crates/project/tests/integration/extension_agent_tests.rs |   4 
crates/remote/src/transport.rs                            |   4 
crates/remote_server/src/remote_editing_tests.rs          |   6 
crates/workspace/src/multi_workspace.rs                   |   3 
crates/workspace/src/workspace.rs                         |   3 
crates/zed/Cargo.toml                                     |   2 
docs/src/development/linux.md                             |  27 -
script/install-mold                                       |  45 --
script/install-wild                                       |  44 --
script/linux                                              |  14 
tooling/xtask/src/tasks/workflows/release.rs              |  26 
25 files changed, 217 insertions(+), 316 deletions(-)

Detailed changes

.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

.github/workflows/compliance_check.yml 🔗

@@ -51,18 +51,13 @@ jobs:
     - name: send_compliance_slack_notification
       if: always()
       run: |
-        REPORT_CONTENT=""
-        if [ -f "compliance-report-${GITHUB_REF_NAME}.md" ]; then
-            REPORT_CONTENT=$(cat "compliance-report-${GITHUB_REF_NAME}.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}')" \

.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-${GITHUB_REF_NAME}.md'
       if: always()
       uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
@@ -325,18 +326,13 @@ jobs:
     - name: send_compliance_slack_notification
       if: always()
       run: |
-        REPORT_CONTENT=""
-        if [ -f "compliance-report-${GITHUB_REF_NAME}.md" ]; then
-            REPORT_CONTENT=$(cat "compliance-report-${GITHUB_REF_NAME}.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}')" \
@@ -685,6 +681,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-${GITHUB_REF_NAME}.md'
       if: always()
       uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
@@ -692,21 +689,17 @@ jobs:
         name: compliance-report-${GITHUB_REF_NAME}.md
         path: compliance-report-${GITHUB_REF_NAME}.md
         if-no-files-found: error
+        overwrite: true
     - name: send_compliance_slack_notification
       if: always()
       run: |
-        REPORT_CONTENT=""
-        if [ -f "compliance-report-${GITHUB_REF_NAME}.md" ]; then
-            REPORT_CONTENT=$(cat "compliance-report-${GITHUB_REF_NAME}.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}')" \

Cargo.lock 🔗

@@ -275,6 +275,7 @@ dependencies = [
  "nix 0.29.0",
  "project",
  "release_channel",
+ "remote",
  "reqwest_client",
  "serde",
  "serde_json",
@@ -22138,7 +22139,7 @@ dependencies = [
 
 [[package]]
 name = "zed"
-version = "0.232.0"
+version = "0.233.0"
 dependencies = [
  "acp_thread",
  "acp_tools",

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 . .

crates/acp_thread/src/connection.rs 🔗

@@ -117,7 +117,7 @@ pub trait AgentConnection {
         &self,
         _method: &acp::AuthMethodId,
         _cx: &App,
-    ) -> Option<SpawnInTerminal> {
+    ) -> Option<Task<Result<SpawnInTerminal>>> {
         None
     }
 

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

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<acp::ClientSideConnection>,
     sessions: Rc<RefCell<HashMap<acp::SessionId, AcpSession>>>,
     auth_methods: Vec<acp::AuthMethod>,
-    command: AgentServerCommand,
+    agent_server_store: WeakEntity<AgentServerStore>,
     agent_capabilities: acp::AgentCapabilities,
     default_mode: Option<acp::SessionModeId>,
     default_model: Option<acp::ModelId>,
@@ -167,6 +167,7 @@ pub async fn connect(
     agent_id: AgentId,
     project: Entity<Project>,
     command: AgentServerCommand,
+    agent_server_store: WeakEntity<AgentServerStore>,
     default_mode: Option<acp::SessionModeId>,
     default_model: Option<acp::ModelId>,
     default_config_options: HashMap<String, String>,
@@ -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<Project>,
         command: AgentServerCommand,
+        agent_server_store: WeakEntity<AgentServerStore>,
         default_mode: Option<acp::SessionModeId>,
         default_model: Option<acp::ModelId>,
         default_config_options: HashMap<String, String>,
         cx: &mut AsyncApp,
     ) -> Result<Self> {
+        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<SpawnInTerminal> {
+    ) -> Option<Task<Result<SpawnInTerminal>>> {
         let method = self
             .auth_methods
             .iter()
@@ -898,9 +919,28 @@ impl AgentConnection for AcpConnection {
 
         match method {
             acp::AuthMethod::Terminal(terminal) if cx.has_flag::<AcpBetaFeatureFlag>() => {
-                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");
     }
 }
 

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,

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!(

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<Self>) -> 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(

crates/auto_update_ui/src/auto_update_ui.rs 🔗

@@ -200,9 +200,9 @@ fn announcement_for_version(version: &Version, cx: &App) -> Option<AnnouncementC
                     description: "Run multiple agent threads simultaneously across projects."
                         .into(),
                     bullet_items: vec![
-                        "Mix and match Zed's agent with any ACP-compatible agent".into(),
-                        "Optional worktree isolation keeps agents from conflicting".into(),
-                        "Updated workspace layout designed for agentic workflows".into(),
+                        "Use your favorite agents in parallel".into(),
+                        "Optionally isolate agents using worktrees".into(),
+                        "Combine multiple projects in one window".into(),
                     ],
                     primary_action_label: "Try Now".into(),
                     primary_action_url: None,

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<String>,
         extra_env: HashMap<String, String>,
-        new_version_available_tx: Option<watch::Sender<Option<String>>>,
         cx: &mut AsyncApp,
     ) -> Task<Result<AgentServerCommand>>;
 
@@ -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<String>,
         extra_env: HashMap<String, String>,
-        new_version_available_tx: Option<watch::Sender<Option<String>>>,
         cx: &mut AsyncApp,
     ) -> Task<Result<AgentServerCommand>> {
         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<String>,
         extra_env: HashMap<String, String>,
-        new_version_available_tx: Option<watch::Sender<Option<String>>>,
         cx: &mut AsyncApp,
     ) -> Task<Result<AgentServerCommand>> {
-        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<String>,
         extra_env: HashMap<String, String>,
-        new_version_available_tx: Option<watch::Sender<Option<String>>>,
         cx: &mut AsyncApp,
     ) -> Task<Result<AgentServerCommand>> {
-        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<String>,
         extra_env: HashMap<String, String>,
-        new_version_available_tx: Option<watch::Sender<Option<String>>>,
         cx: &mut AsyncApp,
     ) -> Task<Result<AgentServerCommand>> {
-        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<String>,
         extra_env: HashMap<String, String>,
-        _new_version_available_tx: Option<watch::Sender<Option<String>>>,
         cx: &mut AsyncApp,
     ) -> Task<Result<AgentServerCommand>> {
         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)
         })
     }

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<String>,
         _extra_env: HashMap<String, String>,
-        _new_version_available_tx: Option<watch::Sender<Option<String>>>,
         _cx: &mut AsyncApp,
     ) -> Task<Result<AgentServerCommand>> {
         Task::ready(Ok(AgentServerCommand {

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<String>,
         _extra_env: HashMap<String, String>,
-        _new_version_available_tx: Option<watch::Sender<Option<String>>>,
         _cx: &mut AsyncApp,
     ) -> Task<Result<AgentServerCommand>> {
         Task::ready(Ok(AgentServerCommand {

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
     {

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()),

crates/workspace/src/multi_workspace.rs 🔗

@@ -586,6 +586,9 @@ impl MultiWorkspace {
     pub fn restore_project_group_keys(&mut self, keys: Vec<ProjectGroupKey>) {
         let mut restored: Vec<ProjectGroupKey> = Vec::with_capacity(keys.len());
         for key in keys {
+            if key.path_list().paths().is_empty() {
+                continue;
+            }
             if !restored.contains(&key) {
                 restored.push(key);
             }

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<ProjectGroupKey> = 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) =

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 <hi@zed.dev>"]

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/).

script/install-mold 🔗

@@ -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 <<EOF
-Mold is installed to /usr/local/bin/mold
-
-To make it your default, add or merge these lines into your ~/.cargo/config.toml:
-
-[target.'cfg(target_os = "linux")']
-linker = "clang"
-rustflags = ["-C", "link-arg=-fuse-ld=mold"]
-EOF

script/install-wild 🔗

@@ -1,44 +0,0 @@
-#!/usr/bin/env bash
-
-# Install wild-linker official binaries from GitHub Releases.
-
-set -euo pipefail
-
-WILD_VERSION="${WILD_VERSION:-${1:-0.6.0}}"
-if [ "$(uname -s)" != "Linux" ]; then
-    echo "Error: This script is intended for Linux systems only."
-    exit 1
-elif [ -z "$WILD_VERSION" ]; then
-    echo "Usage: $0 [version]"
-    exit 1
-elif command -v wild >/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 <<EOF
-Wild is installed to ${DEST_DIR}/wild
-
-To make it your default, add or merge these lines into your ~/.cargo/config.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"]
-EOF

script/linux 🔗

@@ -12,9 +12,6 @@ function finalize {
   # after packages install (curl, etc), get the rust toolchain
   which rustup > /dev/null 2>&1 || curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
   cat <<EOF
-Note: It's recommended to install and configure Wild or Mold for faster builds.
-      Run script/install-wild or script/install-mold.
-
 Finished installing Linux dependencies with script/linux
 EOF
 }
@@ -55,11 +52,11 @@ if [[ -n $apt ]]; then
   )
   if (grep -qP 'PRETTY_NAME="(Debian|Raspbian).+13' /etc/os-release); then
       # libstdc++-14-dev is in build-essential
-      deps+=( mold )
+      true
   elif (grep -qP 'PRETTY_NAME="(Linux Mint 22|.+24\.(04|10))' /etc/os-release); then
-    deps+=( mold libstdc++-14-dev )
+    deps+=( libstdc++-14-dev )
   elif (grep -qP 'PRETTY_NAME="((Debian|Raspbian).+12|Linux Mint 21|.+22\.04)' /etc/os-release); then
-    deps+=( mold libstdc++-12-dev )
+    deps+=( libstdc++-12-dev )
   elif (grep -qP 'PRETTY_NAME="((Debian|Raspbian).+11|Linux Mint 20|.+20\.04)' /etc/os-release); then
     # Ubuntu 20.04 ships clang-10 and libstdc++-10 which lack adequate C++20
     # support for building webrtc-sys (requires -std=c++20, lambdas in
@@ -125,7 +122,6 @@ if [[ -n $dnf ]] || [[ -n $yum ]]; then
       perl-IPC-Cmd
       perl-File-Compare
       perl-File-Copy
-      mold
     )
   elif grep -qP '^ID="?(rhel|rocky|alma|centos|ol)' /etc/os-release; then
     deps+=( perl-interpreter )
@@ -183,7 +179,6 @@ if [[ -n $zyp ]]; then
     libxkbcommon-x11-devel
     libzstd-devel
     make
-    mold
     openssl-devel
     sqlite3-devel
     tar
@@ -217,7 +212,6 @@ if [[ -n $pacman ]]; then
     openssl
     zstd
     pkgconf
-    mold
     sqlite
     pipewire
     xdg-desktop-portal
@@ -250,7 +244,6 @@ if [[ -n $xbps ]]; then
     openssl-devel
     wayland-devel
     vulkan-loader
-    mold
     sqlite-devel
     pipewire
     xdg-desktop-portal
@@ -277,7 +270,6 @@ if [[ -n $emerge ]]; then
     media-libs/vulkan-loader
     x11-libs/libxcb
     x11-libs/libxkbcommon
-    sys-devel/mold
     dev-db/sqlite
     media-video/pipewire
     sys-apps/xdg-desktop-portal

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 },
 }
 
@@ -164,11 +165,16 @@ pub(crate) fn add_compliance_notification_steps(
     job: gh_workflow::Job,
     context: ComplianceContext,
 ) -> gh_workflow::Job {
-    let upload_step =
-        upload_artifact(COMPLIANCE_REPORT_PATH).if_condition(Expression::new("always()"));
+    let upload_step = upload_artifact(COMPLIANCE_REPORT_PATH)
+        .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",
@@ -176,18 +182,13 @@ pub(crate) fn add_compliance_notification_steps(
     };
 
     let script = formatdoc! {r#"
-        REPORT_CONTENT=""
-        if [ -f "{COMPLIANCE_REPORT_PATH}" ]; then
-            REPORT_CONTENT=$(cat "{COMPLIANCE_REPORT_PATH}")
-        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}}')" \
@@ -206,7 +207,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(),
             },
         ))
@@ -226,6 +229,7 @@ fn run_compliance_check() -> Step<Run> {
     .id(COMPLIANCE_STEP_ID)
     .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)
 }
 
 fn compliance_check() -> NamedJob {
@@ -241,7 +245,7 @@ fn compliance_check() -> NamedJob {
 
     named::job(add_compliance_notification_steps(
         job,
-        ComplianceContext::Release,
+        ComplianceContext::ReleaseNonBlocking,
     ))
 }