mirror of
https://github.com/zed-industries/zed.git
synced 2026-05-31 19:05:00 +07:00
Merge branch 'main' into fix-worktree-drag-reorder
This commit is contained in:
commit
214d929281
18 changed files with 309 additions and 345 deletions
|
|
@ -1177,6 +1177,7 @@ pub enum LoadError {
|
|||
FailedToInstall(SharedString),
|
||||
Exited {
|
||||
status: ExitStatus,
|
||||
stderr: Option<SharedString>,
|
||||
},
|
||||
Other(SharedString),
|
||||
}
|
||||
|
|
@ -1195,7 +1196,7 @@ impl Display for LoadError {
|
|||
)
|
||||
}
|
||||
LoadError::FailedToInstall(msg) => write!(f, "Failed to install: {msg}"),
|
||||
LoadError::Exited { status } => write!(f, "Server exited with status {status}"),
|
||||
LoadError::Exited { status, .. } => write!(f, "Server exited with status {status}"),
|
||||
LoadError::Other(msg) => write!(f, "{msg}"),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6061,7 +6061,6 @@ async fn test_edit_file_tool_deny_rule_blocks_edit(cx: &mut TestAppContext) {
|
|||
let task = cx.update(|cx| {
|
||||
tool.run(
|
||||
ToolInput::resolved(crate::EditFileToolInput {
|
||||
display_description: "Edit sensitive file".to_string(),
|
||||
path: "root/sensitive_config.txt".into(),
|
||||
mode: crate::EditFileMode::Edit,
|
||||
content: None,
|
||||
|
|
@ -6496,7 +6495,6 @@ async fn test_edit_file_tool_allow_rule_skips_confirmation(cx: &mut TestAppConte
|
|||
let _task = cx.update(|cx| {
|
||||
tool.run(
|
||||
ToolInput::resolved(crate::EditFileToolInput {
|
||||
display_description: "Edit README".to_string(),
|
||||
path: "root/README.md".into(),
|
||||
mode: crate::EditFileMode::Edit,
|
||||
content: None,
|
||||
|
|
@ -6569,7 +6567,6 @@ async fn test_edit_file_tool_allow_still_prompts_for_local_settings(cx: &mut Tes
|
|||
let _task = cx.update(|cx| {
|
||||
tool.run(
|
||||
ToolInput::resolved(crate::EditFileToolInput {
|
||||
display_description: "Edit local settings".to_string(),
|
||||
path: "root/.zed/settings.json".into(),
|
||||
mode: crate::EditFileMode::Edit,
|
||||
content: None,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -12,10 +12,8 @@ use crate::{AgentTool, ToolCallEventStream, ToolInput};
|
|||
#[serde(rename_all = "snake_case")]
|
||||
#[schemars(inline)]
|
||||
pub enum Timezone {
|
||||
/// Use UTC for the datetime.
|
||||
#[serde(alias = "UTC", alias = "Utc")]
|
||||
Utc,
|
||||
/// Use local time for the datetime.
|
||||
#[serde(alias = "LOCAL", alias = "Local")]
|
||||
Local,
|
||||
}
|
||||
|
|
@ -24,7 +22,7 @@ pub enum Timezone {
|
|||
/// Only use this tool when the user specifically asks for it or the current task would benefit from knowing the current datetime.
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct NowToolInput {
|
||||
/// The timezone to use for the datetime.
|
||||
/// The timezone to use for the datetime. Use `utc` for UTC, or `local` for the system's local time.
|
||||
timezone: Timezone,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -381,7 +381,6 @@ pub fn collect_symlink_escapes<'a>(
|
|||
pub fn authorize_file_edit(
|
||||
tool_name: &str,
|
||||
path: &Path,
|
||||
display_description: &str,
|
||||
thread: &WeakEntity<Thread>,
|
||||
event_stream: &ToolCallEventStream,
|
||||
cx: &mut App,
|
||||
|
|
@ -396,7 +395,7 @@ pub fn authorize_file_edit(
|
|||
}
|
||||
|
||||
let path_owned = path.to_path_buf();
|
||||
let display_description = display_description.to_string();
|
||||
let title = format!("Edit {}", util::markdown::MarkdownInlineCode(&path_str));
|
||||
let tool_name = tool_name.to_string();
|
||||
let thread = thread.clone();
|
||||
let event_stream = event_stream.clone();
|
||||
|
|
@ -486,7 +485,7 @@ pub fn authorize_file_edit(
|
|||
vec![path_owned.to_string_lossy().to_string()],
|
||||
);
|
||||
event_stream.authorize_always_prompt(
|
||||
format!("{} (local settings)", display_description),
|
||||
format!("{title} (local settings)"),
|
||||
context,
|
||||
cx,
|
||||
)
|
||||
|
|
@ -499,11 +498,7 @@ pub fn authorize_file_edit(
|
|||
&tool_name,
|
||||
vec![path_owned.to_string_lossy().to_string()],
|
||||
);
|
||||
event_stream.authorize_always_prompt(
|
||||
format!("{} (settings)", display_description),
|
||||
context,
|
||||
cx,
|
||||
)
|
||||
event_stream.authorize_always_prompt(format!("{title} (settings)"), context, cx)
|
||||
});
|
||||
return authorize.await;
|
||||
}
|
||||
|
|
@ -518,7 +513,7 @@ pub fn authorize_file_edit(
|
|||
&tool_name,
|
||||
vec![path_owned.to_string_lossy().to_string()],
|
||||
);
|
||||
event_stream.authorize(&display_description, context, cx)
|
||||
event_stream.authorize(&title, context, cx)
|
||||
});
|
||||
authorize.await
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ use project::{AgentId, Project};
|
|||
use remote::remote_client::Interactive;
|
||||
use serde::Deserialize;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Stdio;
|
||||
use std::process::{ExitStatus, Stdio};
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{any::Any, cell::RefCell, collections::VecDeque};
|
||||
|
|
@ -195,6 +195,34 @@ impl AcpDebugLog {
|
|||
sender.try_send(message.clone()).log_err();
|
||||
}
|
||||
}
|
||||
|
||||
fn trailing_stderr(&self) -> Option<String> {
|
||||
let state = self.state.lock().ok()?;
|
||||
let mut lines = state
|
||||
.messages
|
||||
.iter()
|
||||
.rev()
|
||||
.take_while(|message| matches!(&message.message, AcpDebugMessageContent::Stderr { .. }))
|
||||
.filter_map(|message| match &message.message {
|
||||
AcpDebugMessageContent::Stderr { line } if !line.is_empty() => Some(line.as_ref()),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if lines.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
lines.reverse();
|
||||
Some(lines.join("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
fn exited_load_error_with_stderr(status: ExitStatus, debug_log: &AcpDebugLog) -> LoadError {
|
||||
LoadError::Exited {
|
||||
status,
|
||||
stderr: debug_log.trailing_stderr().map(SharedString::from),
|
||||
}
|
||||
}
|
||||
|
||||
/// Awaits the response to an ACP request from a GPUI foreground task.
|
||||
|
|
@ -714,6 +742,7 @@ impl AcpConnection {
|
|||
log::trace!("Spawned (pid: {})", child.id());
|
||||
|
||||
let sessions = Rc::new(RefCell::new(HashMap::default()));
|
||||
let debug_log = AcpDebugLog::default();
|
||||
|
||||
let (release_channel, version): (Option<&str>, String) = cx.update(|cx| {
|
||||
(
|
||||
|
|
@ -729,7 +758,6 @@ impl AcpConnection {
|
|||
// Set up the foreground dispatch channel for bridging Send handler
|
||||
// closures to the !Send foreground thread.
|
||||
let (dispatch_tx, dispatch_rx) = mpsc::unbounded::<ForegroundWork>();
|
||||
let debug_log = AcpDebugLog::default();
|
||||
|
||||
let incoming_lines = futures::io::BufReader::new(stdout).lines();
|
||||
let tapped_incoming = incoming_lines.inspect({
|
||||
|
|
@ -756,37 +784,6 @@ impl AcpConnection {
|
|||
|
||||
let transport = Lines::new(tapped_outgoing, tapped_incoming);
|
||||
|
||||
// `connect_client_future` installs the production handler set and
|
||||
// hands us back both the connection-future (to run on a background
|
||||
// executor) and a oneshot receiver that produces the
|
||||
// `ConnectionTo<Agent>` once the transport handshake is ready.
|
||||
let (connection_tx, connection_rx) = futures::channel::oneshot::channel();
|
||||
let connection_future =
|
||||
connect_client_future("zed", transport, dispatch_tx.clone(), connection_tx);
|
||||
let io_task = cx.background_spawn(async move {
|
||||
if let Err(err) = connection_future.await {
|
||||
log::error!("ACP connection error: {err}");
|
||||
}
|
||||
});
|
||||
|
||||
let connection: ConnectionTo<Agent> = connection_rx
|
||||
.await
|
||||
.context("Failed to receive ACP connection handle")?;
|
||||
|
||||
// Set up the foreground dispatch loop to process work items from handlers.
|
||||
let dispatch_context = ClientContext {
|
||||
sessions: sessions.clone(),
|
||||
session_list: client_session_list.clone(),
|
||||
};
|
||||
let dispatch_task = cx.spawn({
|
||||
let mut dispatch_rx = dispatch_rx;
|
||||
async move |cx| {
|
||||
while let Some(work) = dispatch_rx.next().await {
|
||||
work.run(cx, &dispatch_context);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let stderr_task = cx.background_spawn({
|
||||
let debug_log = debug_log.clone();
|
||||
async move {
|
||||
|
|
@ -804,17 +801,53 @@ impl AcpConnection {
|
|||
}
|
||||
});
|
||||
|
||||
let wait_task = cx.spawn({
|
||||
let sessions = sessions.clone();
|
||||
let status_fut = child.status();
|
||||
async move |cx| {
|
||||
let status = status_fut.await?;
|
||||
emit_load_error_to_all_sessions(&sessions, LoadError::Exited { status }, cx);
|
||||
anyhow::Ok(())
|
||||
// `connect_client_future` installs the production handler set and
|
||||
// hands us back both the connection-future (to run on a background
|
||||
// executor) and a oneshot receiver that produces the
|
||||
// `ConnectionTo<Agent>` once the transport handshake is ready.
|
||||
let (connection_tx, connection_rx) = futures::channel::oneshot::channel();
|
||||
let connection_future =
|
||||
connect_client_future("zed", transport, dispatch_tx.clone(), connection_tx);
|
||||
let io_task = cx.background_spawn(async move {
|
||||
if let Err(err) = connection_future.await {
|
||||
log::error!("ACP connection error: {err}");
|
||||
}
|
||||
});
|
||||
|
||||
let response = into_foreground_future(
|
||||
let connection_rx = async move {
|
||||
connection_rx
|
||||
.await
|
||||
.context("Failed to receive ACP connection handle")
|
||||
}
|
||||
.boxed_local();
|
||||
let status_fut = child.status().boxed_local();
|
||||
let (connection, status_fut) = match futures::future::select(connection_rx, status_fut)
|
||||
.await
|
||||
{
|
||||
futures::future::Either::Left((connection, status_fut)) => (connection?, status_fut),
|
||||
futures::future::Either::Right((status, _connection_rx)) => match status {
|
||||
Ok(status) => return Err(exited_load_error_with_stderr(status, &debug_log).into()),
|
||||
Err(err) => {
|
||||
return Err(anyhow!("agent server exited before initialization: {err}"));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Set up the foreground dispatch loop to process work items from handlers.
|
||||
let dispatch_context = ClientContext {
|
||||
sessions: sessions.clone(),
|
||||
session_list: client_session_list.clone(),
|
||||
};
|
||||
let dispatch_task = cx.spawn({
|
||||
let mut dispatch_rx = dispatch_rx;
|
||||
async move |cx| {
|
||||
while let Some(work) = dispatch_rx.next().await {
|
||||
work.run(cx, &dispatch_context);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let initialize_response = into_foreground_future(
|
||||
connection.send_request(
|
||||
acp::InitializeRequest::new(acp::ProtocolVersion::V1)
|
||||
.client_capabilities(
|
||||
|
|
@ -835,12 +868,38 @@ impl AcpConnection {
|
|||
),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
.map(|response| response.map_err(anyhow::Error::from))
|
||||
.boxed_local();
|
||||
let (response, status_fut) = match futures::future::select(initialize_response, status_fut)
|
||||
.await
|
||||
{
|
||||
futures::future::Either::Left((response, status_fut)) => (response?, status_fut),
|
||||
futures::future::Either::Right((status, _initialize_response)) => match status {
|
||||
Ok(status) => return Err(exited_load_error_with_stderr(status, &debug_log).into()),
|
||||
Err(err) => {
|
||||
return Err(anyhow!("agent server exited before initialization: {err}"));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if response.protocol_version < MINIMUM_SUPPORTED_VERSION {
|
||||
return Err(UnsupportedVersion.into());
|
||||
}
|
||||
|
||||
let wait_task = cx.spawn({
|
||||
let sessions = sessions.clone();
|
||||
let debug_log = debug_log.clone();
|
||||
async move |cx| {
|
||||
let status = status_fut.await?;
|
||||
emit_load_error_to_all_sessions(
|
||||
&sessions,
|
||||
exited_load_error_with_stderr(status, &debug_log),
|
||||
cx,
|
||||
);
|
||||
anyhow::Ok(())
|
||||
}
|
||||
});
|
||||
|
||||
let telemetry_id = response
|
||||
.agent_info
|
||||
// Use the one the agent provides if we have one
|
||||
|
|
@ -1881,7 +1940,10 @@ pub mod test_support {
|
|||
while let Ok(status) = exit_rx.recv().await {
|
||||
emit_load_error_to_all_sessions(
|
||||
&connection.sessions,
|
||||
LoadError::Exited { status },
|
||||
LoadError::Exited {
|
||||
status,
|
||||
stderr: None,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
|
@ -2373,6 +2435,85 @@ mod tests {
|
|||
assert_eq!(task.label, "Login");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trailing_stderr_only_uses_final_stderr_block() {
|
||||
let debug_log = AcpDebugLog::default();
|
||||
debug_log.record_line(AcpDebugMessageDirection::Stderr, "stale stderr");
|
||||
debug_log.record_line(
|
||||
AcpDebugMessageDirection::Incoming,
|
||||
r#"{"method":"initialized"}"#,
|
||||
);
|
||||
|
||||
assert_eq!(debug_log.trailing_stderr(), None);
|
||||
|
||||
debug_log.record_line(AcpDebugMessageDirection::Stderr, "recent stderr");
|
||||
assert_eq!(
|
||||
debug_log.trailing_stderr().as_deref(),
|
||||
Some("recent stderr")
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[gpui::test]
|
||||
async fn startup_returns_error_when_agent_exits_before_initialization(
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
cx.update(|cx| {
|
||||
let store = settings::SettingsStore::test(cx);
|
||||
cx.set_global(store);
|
||||
});
|
||||
cx.executor().allow_parking();
|
||||
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let project = project::Project::example([temp_dir.path()], &mut cx.to_async()).await;
|
||||
let agent_server_store =
|
||||
project.read_with(cx, |project, _| project.agent_server_store().downgrade());
|
||||
let command = AgentServerCommand {
|
||||
path: "/bin/sh".into(),
|
||||
args: vec![
|
||||
"-c".into(),
|
||||
r#"printf '%s\n' 'npm error code ETARGET' 'npm error notarget No matching version found for @agentclientprotocol/claude-agent-acp@0.32.0 with a date before 4/28/2026, 12:11:38 PM.' >&2; exit 1"#.into(),
|
||||
],
|
||||
env: None,
|
||||
};
|
||||
|
||||
let mut async_cx = cx.to_async();
|
||||
let startup = AcpConnection::stdio(
|
||||
AgentId::new("test-agent"),
|
||||
project,
|
||||
command,
|
||||
agent_server_store,
|
||||
None,
|
||||
None,
|
||||
HashMap::default(),
|
||||
&mut async_cx,
|
||||
)
|
||||
.fuse();
|
||||
let timeout = cx
|
||||
.background_executor
|
||||
.timer(std::time::Duration::from_secs(5))
|
||||
.fuse();
|
||||
futures::pin_mut!(startup, timeout);
|
||||
|
||||
let result = futures::select! {
|
||||
result = startup => result,
|
||||
_ = timeout => panic!("timed out waiting for failed ACP startup"),
|
||||
};
|
||||
|
||||
let Err(error) = result else {
|
||||
panic!("expected ACP startup to fail");
|
||||
};
|
||||
let load_error = error
|
||||
.downcast::<LoadError>()
|
||||
.expect("startup failure should preserve the typed load error");
|
||||
match load_error {
|
||||
LoadError::Exited { status, .. } => {
|
||||
assert!(!status.success(), "expected non-zero exit status");
|
||||
}
|
||||
error => panic!("expected exited load error, got: {error:?}"),
|
||||
};
|
||||
}
|
||||
|
||||
async fn connect_fake_agent(
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) -> (
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ file_icons.workspace = true
|
|||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
git.workspace = true
|
||||
git_ui.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
gpui_tokio.workspace = true
|
||||
|
|
@ -124,6 +123,7 @@ clock = { workspace = true, features = ["test-support"] }
|
|||
db = { workspace = true, features = ["test-support"] }
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
eval_utils.workspace = true
|
||||
git_ui.workspace = true
|
||||
gpui = { workspace = true, "features" = ["test-support"] }
|
||||
http_client = { workspace = true, features = ["test-support"] }
|
||||
indoc.workspace = true
|
||||
|
|
|
|||
|
|
@ -2159,11 +2159,17 @@ impl ConversationView {
|
|||
msg.into(),
|
||||
Some(self.create_copy_button(msg.to_string()).into_any_element()),
|
||||
),
|
||||
LoadError::Exited { status } => (
|
||||
"Failed to Launch",
|
||||
format!("Server exited with status {status}").into(),
|
||||
None,
|
||||
),
|
||||
LoadError::Exited { status, stderr } => {
|
||||
let mut message = format!("Server exited with status {status}");
|
||||
if let Some(stderr) = stderr {
|
||||
message.push_str("\n");
|
||||
message.push_str(stderr);
|
||||
};
|
||||
let action_slot = stderr
|
||||
.is_some()
|
||||
.then(|| self.create_copy_button(message.clone()).into_any_element());
|
||||
("Failed to Launch", message.into(), action_slot)
|
||||
}
|
||||
LoadError::Other(msg) => (
|
||||
"Failed to Launch",
|
||||
msg.into(),
|
||||
|
|
|
|||
|
|
@ -449,7 +449,7 @@ fn create_editor_diff(
|
|||
editor.set_show_vertical_scrollbar(false, cx);
|
||||
editor.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
|
||||
editor.set_soft_wrap_mode(SoftWrap::None, cx);
|
||||
editor.scroll_manager.set_forbid_vertical_scroll(true);
|
||||
editor.set_forbid_vertical_scroll(true);
|
||||
editor.set_show_indent_guides(false, cx);
|
||||
editor.set_read_only(true);
|
||||
editor.set_delegate_open_excerpts(true);
|
||||
|
|
|
|||
|
|
@ -1425,7 +1425,7 @@ impl InlineAssistant {
|
|||
editor.set_show_gutter(false, cx);
|
||||
editor.set_offset_content(false, cx);
|
||||
editor.disable_mouse_wheel_zoom();
|
||||
editor.scroll_manager.set_forbid_vertical_scroll(true);
|
||||
editor.set_forbid_vertical_scroll(true);
|
||||
editor.set_read_only(true);
|
||||
editor.set_show_edit_predictions(Some(false), window, cx);
|
||||
editor.highlight_rows::<DeletedLines>(
|
||||
|
|
|
|||
|
|
@ -1157,7 +1157,12 @@ pub struct Editor {
|
|||
pub display_map: Entity<DisplayMap>,
|
||||
placeholder_display_map: Option<Entity<DisplayMap>>,
|
||||
pub selections: SelectionsCollection,
|
||||
pub scroll_manager: ScrollManager,
|
||||
/// Manages the scroll position for the given editor.
|
||||
///
|
||||
/// Whenever you want to modify the scroll position of the editor, you should
|
||||
/// usually use the existing available APIs as opposed to directly interacting
|
||||
/// with the scroll manager.
|
||||
pub(crate) scroll_manager: ScrollManager,
|
||||
/// When inline assist editors are linked, they all render cursors because
|
||||
/// typing enters text into each of them, even the ones that aren't focused.
|
||||
pub(crate) show_cursor_when_unfocused: bool,
|
||||
|
|
|
|||
|
|
@ -623,6 +623,14 @@ impl Editor {
|
|||
self.scroll_manager.has_autoscroll_request()
|
||||
}
|
||||
|
||||
pub fn set_forbid_vertical_scroll(&mut self, forbid: bool) {
|
||||
self.scroll_manager.set_forbid_vertical_scroll(forbid);
|
||||
}
|
||||
|
||||
pub fn scroll_top_display_point(&self, snapshot: &DisplaySnapshot, cx: &App) -> DisplayPoint {
|
||||
self.scroll_manager.scroll_top_display_point(snapshot, cx)
|
||||
}
|
||||
|
||||
pub fn vertical_scroll_margin(&self) -> usize {
|
||||
self.scroll_manager.vertical_scroll_margin as usize
|
||||
}
|
||||
|
|
|
|||
1
crates/eval_cli/.gitignore
vendored
1
crates/eval_cli/.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
|||
**/jobs
|
||||
**/*.egg-info
|
||||
**/__pycache__
|
||||
uv.lock
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@
|
|||
# Or use the helper script:
|
||||
# crates/eval_cli/script/build-linux
|
||||
|
||||
FROM rust:1.94.1 AS builder
|
||||
FROM rust:1.95.0 AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Pre-install the toolchain specified in rust-toolchain.toml so it is cached.
|
||||
RUN rustup toolchain install 1.94.1 --profile minimal \
|
||||
RUN rustup toolchain install 1.95.0 --profile minimal \
|
||||
--component rustfmt --component clippy --component rust-analyzer --component rust-src \
|
||||
--target wasm32-wasip2 --target wasm32-unknown-unknown --target x86_64-unknown-linux-musl --target x86_64-unknown-linux-gnu
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ struct Args {
|
|||
workdir: PathBuf,
|
||||
|
||||
/// Instruction/prompt text. If omitted, read from --instruction-file or stdin.
|
||||
#[arg(long)]
|
||||
#[arg(long, allow_hyphen_values = true)]
|
||||
instruction: Option<String>,
|
||||
|
||||
/// Language model to use, in `provider/model` format.
|
||||
|
|
|
|||
|
|
@ -52,19 +52,20 @@ class ZedAgent(BaseInstalledAgent):
|
|||
return "zed"
|
||||
|
||||
async def _detect_workdir(self, environment: BaseEnvironment) -> str:
|
||||
"""Detect the repo working directory inside the container.
|
||||
"""Detect the working directory inside the container.
|
||||
|
||||
Checks, in order:
|
||||
1. Explicit ``EVAL_CLI_WORKDIR`` extra-env override
|
||||
2. ``/app`` (SWE-bench Pro)
|
||||
3. ``/testbed`` (SWE-bench Verified)
|
||||
4. ``/repo``
|
||||
5. First git repo found under ``/`` (max depth 3)
|
||||
2. Well-known dirs with a ``.git`` subdirectory (SWE-bench style)
|
||||
3. First git repo found under ``/`` (max depth 3)
|
||||
4. Well-known dirs that exist at all (terminal-bench style)
|
||||
5. The container's default working directory (``pwd``)
|
||||
"""
|
||||
override = self._extra_env.get("EVAL_CLI_WORKDIR")
|
||||
if override:
|
||||
return override
|
||||
|
||||
# First: try to find a git repo (SWE-bench, etc.)
|
||||
result = await self.exec_as_agent(
|
||||
environment,
|
||||
command=(
|
||||
|
|
@ -75,13 +76,29 @@ class ZedAgent(BaseInstalledAgent):
|
|||
'| head -1 | sed "s|/.git$||"'
|
||||
),
|
||||
)
|
||||
workdir = result.stdout.strip()
|
||||
if not workdir:
|
||||
raise RuntimeError(
|
||||
"Could not find a git repository in the container. "
|
||||
"Set EVAL_CLI_WORKDIR explicitly via --ae EVAL_CLI_WORKDIR=/path/to/repo"
|
||||
)
|
||||
return workdir
|
||||
workdir = (result.stdout or "").strip()
|
||||
if workdir:
|
||||
return workdir
|
||||
|
||||
# Fallback: use the first well-known directory that exists,
|
||||
# even without .git (terminal-bench containers aren't git repos).
|
||||
result = await self.exec_as_agent(
|
||||
environment,
|
||||
command=(
|
||||
"for d in /app /testbed /repo /root /home; do "
|
||||
' if [ -d "$d" ]; then echo "$d"; exit 0; fi; '
|
||||
"done; "
|
||||
"pwd"
|
||||
),
|
||||
)
|
||||
workdir = (result.stdout or "").strip()
|
||||
if workdir:
|
||||
return workdir
|
||||
|
||||
raise RuntimeError(
|
||||
"Could not detect a working directory in the container. "
|
||||
"Set EVAL_CLI_WORKDIR explicitly via --ae EVAL_CLI_WORKDIR=/path/to/repo"
|
||||
)
|
||||
|
||||
async def install(self, environment: BaseEnvironment) -> None:
|
||||
# Detect the package manager and install base dependencies.
|
||||
|
|
@ -426,12 +443,18 @@ class ZedAgent(BaseInstalledAgent):
|
|||
env=env,
|
||||
)
|
||||
|
||||
# Only generate a patch if the workdir is a git repo
|
||||
# (SWE-bench style). Terminal-bench containers aren't git repos.
|
||||
await self.exec_as_agent(
|
||||
environment,
|
||||
command=(
|
||||
'if [ -d ".git" ]; then '
|
||||
"git add -A && "
|
||||
"git diff --cached HEAD > /logs/agent/patch.diff && "
|
||||
'echo "Patch size: $(wc -c < /logs/agent/patch.diff) bytes"'
|
||||
'echo "Patch size: $(wc -c < /logs/agent/patch.diff) bytes"; '
|
||||
"else "
|
||||
'echo "No git repo found, skipping patch generation"; '
|
||||
"fi"
|
||||
),
|
||||
cwd=workdir,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ name = "zed-eval"
|
|||
version = "0.1.0"
|
||||
description = "Harbor agent wrapper for Zed's eval-cli"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["harbor"]
|
||||
dependencies = ["harbor==0.6.4"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools"]
|
||||
|
|
|
|||
|
|
@ -109,9 +109,7 @@ impl Vim {
|
|||
self.update_editor(cx, |vim, editor, cx| {
|
||||
let should_move_cursor = editor.newest_selection_on_screen(cx).is_eq();
|
||||
let display_snapshot = editor.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let old_top = editor
|
||||
.scroll_manager
|
||||
.scroll_top_display_point(&display_snapshot, cx);
|
||||
let old_top = editor.scroll_top_display_point(&display_snapshot, cx);
|
||||
|
||||
if editor.scroll_hover(amount, window, cx) {
|
||||
return;
|
||||
|
|
@ -143,9 +141,7 @@ impl Vim {
|
|||
};
|
||||
|
||||
let display_snapshot = editor.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let top = editor
|
||||
.scroll_manager
|
||||
.scroll_top_display_point(&display_snapshot, cx);
|
||||
let top = editor.scroll_top_display_point(&display_snapshot, cx);
|
||||
let vertical_scroll_margin = EditorSettings::get_global(cx).vertical_scroll_margin;
|
||||
|
||||
let mut move_cursor = |map: &editor::display_map::DisplaySnapshot,
|
||||
|
|
|
|||
Loading…
Reference in a new issue