Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
0bcf71f
vim: Add C preprocessor check in matching function (#55515)
tanaxer01 May 7, 2026
6aa90e7
docs: Update actions format (#54869)
lingyaochu May 7, 2026
0a52f80
acp_thread: Clear running_turn when prompt task drops tx (#55562)
brunorodmoreira May 7, 2026
7fcc4ba
eslint: Fix `workspaceFolder.uri` sent as raw path instead of `file:/…
YangChengxxyy May 7, 2026
42017bc
agent: Handle out of order old_text/new_text in edit file tool (#55894)
bennetbo May 7, 2026
b3a67c9
markdown_preview: Implement reload (#56016)
benbrandt May 7, 2026
c6e9a95
x_ai: Update models list (#55931)
bennetbo May 7, 2026
7d19e89
Fix DirectX atlas panic after GPU device recovery (#55878)
benbrandt May 7, 2026
47ea7de
Fix leak detector causing panics in unit evals (#56029)
bennetbo May 7, 2026
59daeba
vim: Add setting to control whether edit predictions are shown in nor…
probably-neb May 7, 2026
e6b8b30
markdown: Improve table cell alignment (#53465)
davidalecrim1 May 7, 2026
9a125a5
agent_ui: Preserve selection mentions when starting a new thread (#55…
NeelChotai May 7, 2026
4f54a04
agent_ui: Restore `Ctrl + >` behavior for whole lines (#54698)
NeelChotai May 7, 2026
bd2fb74
editor: Extract `completions` and `code_actions` out of `editor.rs` (…
GoldStrikeArch May 7, 2026
7556cf8
agent: Fix race-condition for LSP tool registration (#56044)
bennetbo May 7, 2026
5c0b33f
gpui_windows: Avoid process-wide priority elevation (#56050)
Veykril May 7, 2026
07c1943
Add telemetry events for agent profile usage and configuration (#56054)
macraig May 7, 2026
bfe5dfb
gpui_wgpu: Remove redundant match arms for backend priority (#56032)
cppcoffee May 7, 2026
675ed70
Fix multibuffer initialization based on RHS state (#56058)
chirivelli May 7, 2026
5fc8a83
sidebar: Experimental Terminal Mode (#56063)
benbrandt May 7, 2026
a6f41d1
Fix sign in disclaimer to accurately show trial benefits (#55964)
katie-z-geer May 7, 2026
68256f2
git: Add `dev: show git job queue` (#55904)
cameron1024 May 7, 2026
1475248
editor: Extract `fold` and `selection` out of `editor.rs` (#56070)
GoldStrikeArch May 7, 2026
10afe2f
git: Make `git::Commit` do an amend when amending is pending (#54472)
cole-miller May 7, 2026
8624bf6
git: Fix diff hunks not being removed on restore in remote projects (…
cole-miller May 7, 2026
1b88528
agent_ui: Handle Cut for selection mentions (#54694)
NeelChotai May 7, 2026
8bdcce8
settings_ui: Stop reading the clipboard on every frame (#56075)
agu-z May 7, 2026
ebc46d7
Update rmcp and rpassword (#56096)
benbrandt May 7, 2026
dccea21
auto_update: Add NixOS rsync install hint (#56097)
karol-broda May 7, 2026
6766514
Improve auto watch (#56126)
JosephTLyons May 8, 2026
e1a46f9
gpui: Use `SharedString::new_static` within `From` impls for `Element…
MrSubidubi May 8, 2026
e727080
Update Mistral provider docs following #55443 (#56133)
dargor May 8, 2026
c8f0026
gpui: Remove unsound await_on_background helper (#56132)
Veykril May 8, 2026
4fbce46
Merge upstream/main into main
claude May 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions assets/keymaps/default-linux.json
Original file line number Diff line number Diff line change
Expand Up @@ -1247,6 +1247,12 @@
"ctrl->": "agent::AddSelectionToThread",
},
},
{
"context": "AgentPanel && Terminal",
"bindings": {
"ctrl-n": "agent::NewThread",
},
},
{
"context": "ZedPredictModal",
"bindings": {
Expand Down
7 changes: 7 additions & 0 deletions assets/keymaps/default-macos.json
Original file line number Diff line number Diff line change
Expand Up @@ -1316,6 +1316,13 @@
"cmd->": "agent::AddSelectionToThread",
},
},
{
"context": "AgentPanel > Terminal",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "agent::NewThread",
},
},
{
"context": "RatePredictionsModal",
"use_key_equivalents": true,
Expand Down
7 changes: 7 additions & 0 deletions assets/keymaps/default-windows.json
Original file line number Diff line number Diff line change
Expand Up @@ -1262,6 +1262,13 @@
"ctrl-shift-.": "agent::AddSelectionToThread",
},
},
{
"context": "AgentPanel > Terminal",
"use_key_equivalents": true,
"bindings": {
"ctrl-n": "agent::NewThread",
},
},
{
"context": "Terminal && selection",
"bindings": {
Expand Down
3 changes: 3 additions & 0 deletions assets/settings/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -2515,6 +2515,9 @@
"gdefault": false,
"highlight_on_yank_duration": 200,
"custom_digraphs": {},
// When enabled, edit predictions are shown in Vim normal mode.
// By default, edit predictions are only shown in insert and replace modes.
"show_edit_predictions_in_normal_mode": false,
// Cursor shape for each mode.
// The shape can be one of the following: "block", "bar", "underline", "hollow".
"cursor_shape": {
Expand Down
72 changes: 67 additions & 5 deletions crates/acp_thread/src/acp_thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2294,10 +2294,6 @@ impl AcpThread {
this.project
.update(cx, |project, cx| project.set_agent_location(None, cx));
}
let Ok(response) = response else {
// tx dropped, just return
return Ok(None);
};

let is_same_turn = this
.running_turn
Expand All @@ -2306,11 +2302,18 @@ impl AcpThread {

// If the user submitted a follow up message, running_turn might
// already point to a different turn. Therefore we only want to
// take the task if it's the same turn.
// take the task if it's the same turn. We do this before the
// dropped-tx guard below so the panel exits its generating
// state even when the send_task is cancelled before tx.send().
if is_same_turn {
this.running_turn.take();
}

let Ok(response) = response else {
// tx dropped, just return
return Ok(None);
};

match response {
Ok(r) => {
Self::flush_streaming_text(&mut this.streaming_text_buffer, cx);
Expand Down Expand Up @@ -5517,4 +5520,63 @@ mod tests {
);
});
}

/// Regression test: if the inner send_task is cancelled before it can
/// fire `tx.send(...)` (e.g. because the underlying future was dropped),
/// the outer task observes `rx.await` returning `Err(Cancelled)` and
/// must still clear `running_turn` so the panel transitions out of
/// `Generating`. Without this, the agent thread is wedged in the
/// loading state until Zed restarts.
#[gpui::test]
async fn test_running_turn_cleared_when_send_task_dropped(cx: &mut TestAppContext) {
init_test(cx);

let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, [], cx).await;

// Handler hangs forever so the spawn at run_turn is parked inside
// `f(this, cx).await` with `tx` still alive but unsent.
let connection = Rc::new(FakeAgentConnection::new().on_user_message(
|_params, _thread, _cx| {
async move { futures::future::pending::<Result<acp::PromptResponse>>().await }
.boxed_local()
},
));

let thread = cx
.update(|cx| {
connection.new_session(project, PathList::new(&[Path::new(path!("/test"))]), cx)
})
.await
.unwrap();

let request = thread.update(cx, |thread, cx| thread.send_raw("hello", cx));
cx.run_until_parked();

assert_eq!(
thread.read_with(cx, |t, _| t.status()),
ThreadStatus::Generating,
"thread should be generating while the handler is parked"
);

// Replace the in-flight send_task with a no-op. Dropping the original
// Task cancels its inner future, which drops `tx` without ever calling
// `tx.send(...)`. This mirrors the production scenario where the
// send_task future is cancelled before completion.
thread.update(cx, |thread, _| {
thread.running_turn.as_mut().unwrap().send_task = Task::ready(());
});

let result = request.await;
assert!(
matches!(result, Ok(None)),
"outer task should resolve to Ok(None) on dropped tx, got {result:?}"
);

assert_eq!(
thread.read_with(cx, |t, _| t.status()),
ThreadStatus::Idle,
"running_turn must be cleared even when tx was dropped without send"
);
}
}
99 changes: 99 additions & 0 deletions crates/agent/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5545,6 +5545,105 @@ async fn test_max_subagent_depth_prevents_tool_registration(cx: &mut TestAppCont
});
}

#[gpui::test]
async fn test_lsp_tools_gated_by_feature_flag(cx: &mut TestAppContext) {
init_test(cx);

let fs = FakeFs::new(cx.executor());
fs.insert_tree(path!("/test"), json!({})).await;
let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
let project_context = cx.new(|_cx| ProjectContext::default());
let context_server_store = project.read_with(cx, |project, _| project.context_server_store());
let context_server_registry =
cx.new(|cx| ContextServerRegistry::new(context_server_store.clone(), cx));
let model = Arc::new(FakeLanguageModel::default());
let environment = Rc::new(cx.update(|cx| {
FakeThreadEnvironment::default().with_terminal(FakeTerminalHandle::new_never_exits(cx))
}));

let thread = cx.new(|cx| {
let mut thread = Thread::new(
project,
project_context,
context_server_registry,
Templates::new(),
Some(model.clone() as Arc<dyn LanguageModel>),
cx,
);
thread.add_default_tools(environment, cx);
thread
});

let lsp_tool_names = [
FindReferencesTool::NAME,
GetCodeActionsTool::NAME,
ApplyCodeActionTool::NAME,
GoToDefinitionTool::NAME,
RenameTool::NAME,
];

// All LSP tools should be registered on the thread regardless of the flag,
// since the feature flag now only controls exposure to the model rather
// than registration.
thread.read_with(cx, |thread, _| {
for name in &lsp_tool_names {
assert!(
thread.has_registered_tool(name),
"expected LSP tool {name} to be registered"
);
}
});

// Without the `lsp-tool` flag, sending a message should produce a
// completion request whose tool list excludes the LSP tools.
thread
.update(cx, |thread, cx| {
thread.send(UserMessageId::new(), ["hello"], cx)
})
.unwrap();
cx.run_until_parked();

let completion = model.pending_completions().pop().unwrap();
let tool_names = tool_names_for_completion(&completion);
for name in &lsp_tool_names {
assert!(
!tool_names.iter().any(|t| t == name),
"expected LSP tool {name} to be hidden without the lsp-tool flag, \
but completion tools were: {tool_names:?}"
);
}
// Sanity check: a non-LSP default tool should still be exposed.
assert!(
tool_names.iter().any(|t| t == ReadFileTool::NAME),
"expected non-LSP tools to still be exposed, got: {tool_names:?}"
);
model.end_last_completion_stream();
cx.run_until_parked();

// Enable the `lsp-tool` flag and send another message; the LSP tools
// should now appear in the completion request.
cx.update(|cx| {
cx.update_flags(false, vec!["lsp-tool".to_string()]);
});

thread
.update(cx, |thread, cx| {
thread.send(UserMessageId::new(), ["hello again"], cx)
})
.unwrap();
cx.run_until_parked();

let completion = model.pending_completions().pop().unwrap();
let tool_names = tool_names_for_completion(&completion);
for name in &lsp_tool_names {
assert!(
tool_names.iter().any(|t| t == name),
"expected LSP tool {name} to be exposed when lsp-tool flag is on, \
but completion tools were: {tool_names:?}"
);
}
}

#[gpui::test]
async fn test_parent_cancel_stops_subagent(cx: &mut TestAppContext) {
init_test(cx);
Expand Down
42 changes: 24 additions & 18 deletions crates/agent/src/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1577,20 +1577,19 @@ impl Thread {
self.add_tool(WebSearchTool);

self.add_tool(DiagnosticsTool::new(self.project.clone()));
if cx.has_flag::<LspToolFeatureFlag>() {
let code_action_store: CodeActionStore = cx.new(|_cx| None);
self.add_tool(FindReferencesTool::new(self.project.clone()));
self.add_tool(GetCodeActionsTool::new(
self.project.clone(),
code_action_store.clone(),
));
self.add_tool(ApplyCodeActionTool::new(
self.project.clone(),
code_action_store,
));
self.add_tool(GoToDefinitionTool::new(self.project.clone()));
self.add_tool(RenameTool::new(self.project.clone()));
}

let code_action_store: CodeActionStore = cx.new(|_cx| None);
self.add_tool(FindReferencesTool::new(self.project.clone()));
self.add_tool(GetCodeActionsTool::new(
self.project.clone(),
code_action_store.clone(),
));
self.add_tool(ApplyCodeActionTool::new(
self.project.clone(),
code_action_store,
));
self.add_tool(GoToDefinitionTool::new(self.project.clone()));
self.add_tool(RenameTool::new(self.project.clone()));

if self.depth() < MAX_SUBAGENT_DEPTH {
self.add_tool(SpawnAgentTool::new(environment));
Expand Down Expand Up @@ -2894,6 +2893,17 @@ impl Thread {
None
}
})
.filter(|(tool_name, _)| {
cx.has_flag::<LspToolFeatureFlag>()
|| !matches!(
tool_name.as_ref(),
FindReferencesTool::NAME
| GetCodeActionsTool::NAME
| ApplyCodeActionTool::NAME
| GoToDefinitionTool::NAME
| RenameTool::NAME
)
})
.collect::<BTreeMap<_, _>>();

let mut context_server_tools = Vec::new();
Expand Down Expand Up @@ -2957,10 +2967,6 @@ impl Thread {
self.tools.contains_key(name)
}

pub fn registered_tool_names(&self) -> Vec<SharedString> {
self.tools.keys().cloned().collect()
}

pub(crate) fn register_running_subagent(&mut self, subagent: WeakEntity<Thread>) {
self.running_subagents.push(subagent);
}
Expand Down
Loading
Loading