使用rust which包来查找命令路径,解决部分电脑环境变量识别问题

This commit is contained in:
xintaofei
2026-03-18 22:59:12 +08:00
parent 0209de17eb
commit e3784fb3f3
5 changed files with 55 additions and 27 deletions

27
src-tauri/Cargo.lock generated
View File

@@ -409,7 +409,7 @@ dependencies = [
"rustc-hash", "rustc-hash",
"shlex", "shlex",
"syn 2.0.114", "syn 2.0.114",
"which", "which 4.4.2",
] ]
[[package]] [[package]]
@@ -827,6 +827,7 @@ dependencies = [
"urlencoding", "urlencoding",
"uuid", "uuid",
"walkdir", "walkdir",
"which 7.0.3",
"windows-sys 0.59.0", "windows-sys 0.59.0",
"zip 2.4.2", "zip 2.4.2",
] ]
@@ -1422,6 +1423,12 @@ dependencies = [
"syn 2.0.114", "syn 2.0.114",
] ]
[[package]]
name = "env_home"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@@ -7185,6 +7192,18 @@ dependencies = [
"rustix 0.38.44", "rustix 0.38.44",
] ]
[[package]]
name = "which"
version = "7.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762"
dependencies = [
"either",
"env_home",
"rustix 1.1.3",
"winsafe",
]
[[package]] [[package]]
name = "whoami" name = "whoami"
version = "1.6.1" version = "1.6.1"
@@ -7762,6 +7781,12 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "winsafe"
version = "0.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
[[package]] [[package]]
name = "wit-bindgen" name = "wit-bindgen"
version = "0.51.0" version = "0.51.0"

View File

@@ -48,6 +48,7 @@ notify = "6"
base64 = "0.22" base64 = "0.22"
agent-client-protocol-schema = { version = "0.10", features = ["unstable_session_usage", "unstable_session_fork"] } agent-client-protocol-schema = { version = "0.10", features = ["unstable_session_usage", "unstable_session_fork"] }
kill_tree = { version = "0.2", features = ["tokio"] } kill_tree = { version = "0.2", features = ["tokio"] }
which = "7"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-window-state = "2" tauri-plugin-window-state = "2"

View File

@@ -131,9 +131,13 @@ async fn build_agent(
parts.push(format!("{k}={v}")); parts.push(format!("{k}={v}"));
} }
parts.push( parts.push(
crate::process::normalized_program(cmd) which::which(cmd)
.to_string_lossy() .map(|p| p.to_string_lossy().to_string())
.to_string(), .unwrap_or_else(|_| {
crate::process::normalized_program(cmd)
.to_string_lossy()
.to_string()
}),
); );
for a in args { for a in args {
parts.push((*a).into()); parts.push((*a).into());

View File

@@ -93,14 +93,24 @@ async fn check_npm_environment(node_required: Option<&str>) -> Vec<CheckItem> {
return checks; return checks;
} }
// Run node and npm checks in parallel // Resolve absolute paths via `which` crate to avoid GUI PATH issues,
// then run version checks in parallel.
let node_path = which::which("node").ok();
let npm_path = which::which("npm").ok();
let (node_result, npm_result) = tokio::join!( let (node_result, npm_result) = tokio::join!(
crate::process::tokio_command("node") async {
.arg("--version") match &node_path {
.output(), Some(p) => crate::process::tokio_command(p).arg("--version").output().await,
crate::process::tokio_command("npm") None => Err(std::io::Error::new(std::io::ErrorKind::NotFound, "node not found in PATH")),
.arg("--version") }
.output(), },
async {
match &npm_path {
Some(p) => crate::process::tokio_command(p).arg("--version").output().await,
None => Err(std::io::Error::new(std::io::ErrorKind::NotFound, "npm not found in PATH")),
}
},
); );
// Track the raw node version string for reuse in the version check // Track the raw node version string for reuse in the version check

View File

@@ -81,27 +81,15 @@ fn package_name_from_spec(package: &str) -> String {
/// Check whether a command is available on the system PATH. /// Check whether a command is available on the system PATH.
/// Uses `which` on unix and `where` on windows — lightweight and does not /// Uses `which` on unix and `where` on windows — lightweight and does not
/// invoke the target binary itself, avoiding side-effects or slow startups. /// invoke the target binary itself, avoiding side-effects or slow startups.
async fn is_cmd_available(cmd: &str) -> bool { fn is_cmd_available(cmd: &str) -> bool {
#[cfg(unix)] which::which(cmd).is_ok()
let check_cmd = "which";
#[cfg(windows)]
let check_cmd = "where";
crate::process::tokio_command(check_cmd)
.arg(cmd)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.await
.map(|s| s.success())
.unwrap_or(false)
} }
async fn detect_local_version(agent_type: AgentType) -> Option<String> { async fn detect_local_version(agent_type: AgentType) -> Option<String> {
let meta = registry::get_agent_meta(agent_type); let meta = registry::get_agent_meta(agent_type);
match meta.distribution { match meta.distribution {
registry::AgentDistribution::Npx { cmd, package, .. } => { registry::AgentDistribution::Npx { cmd, package, .. } => {
if is_cmd_available(cmd).await { if is_cmd_available(cmd) {
version_from_package_spec(package) version_from_package_spec(package)
} else { } else {
None None
@@ -1047,7 +1035,7 @@ pub async fn acp_connect(
} }
if let registry::AgentDistribution::Npx { cmd, .. } = meta.distribution { if let registry::AgentDistribution::Npx { cmd, .. } = meta.distribution {
if !is_cmd_available(cmd).await { if !is_cmd_available(cmd) {
return Err(AcpError::protocol(format!( return Err(AcpError::protocol(format!(
"{} SDK is not installed. Please install it in Agent Settings.", "{} SDK is not installed. Please install it in Agent Settings.",
meta.name meta.name