From 50df18067b2000fd0dc96147d27d047112b6b5da Mon Sep 17 00:00:00 2001 From: lzt404 <2596933790@qq.com> Date: Wed, 26 Aug 2026 16:10:46 +0800 Subject: [PATCH] fix(agent): propagate discovered PATH to agent subprocesses Prioritize the agent executable directory while preserving discovered search paths for Pi package operations and CLI version probes. Add coverage for PATH ordering and downstream command resolution. --- src-tauri/src/agents/discovery.rs | 55 +++++++++++++++---- src-tauri/src/tests/agent_configuration.rs | 61 ++++++++++++++++++++++ 2 files changed, 107 insertions(+), 9 deletions(-) diff --git a/src-tauri/src/agents/discovery.rs b/src-tauri/src/agents/discovery.rs index 8db1c89..3aa40bd 100644 --- a/src-tauri/src/agents/discovery.rs +++ b/src-tauri/src/agents/discovery.rs @@ -396,7 +396,9 @@ pub(crate) fn inspect_pi_provider_status( "unconfigured" }; let error = errors.into_iter().next(); - let cli_version = executable.as_deref().and_then(read_agent_version); + let cli_version = executable + .as_deref() + .and_then(|path| read_agent_version(path, home)); let plugin_version = read_pi_provider_version(home).ok().flatten(); AgentConfigStatus { id: PI_AGENT_ID.to_string(), @@ -559,6 +561,31 @@ pub(crate) fn uninstall_pi_provider_inner( )) } +pub(crate) fn agent_command_path( + home: &Path, + executable: &Path, +) -> Result { + let mut directories = agent_executable_directories(home); + if let Some(executable_directory) = executable + .parent() + .filter(|path| !path.as_os_str().is_empty()) + { + directories.retain(|directory| directory != executable_directory); + directories.insert(0, executable_directory.to_path_buf()); + } + + env::join_paths(directories).map_err(|error| format!("构造智能体命令 PATH 失败: {error}")) +} + +pub(crate) fn configure_agent_command_environment( + command: &mut Command, + home: &Path, + executable: &Path, +) -> Result<(), String> { + command.env("PATH", agent_command_path(home, executable)?); + Ok(()) +} + pub(crate) fn install_pi_package( executable: &Path, home: &Path, @@ -576,6 +603,7 @@ pub(crate) fn install_pi_package( .stdin(Stdio::null()); configure_background_command(&mut command); configure_networked_command(&mut command, proxy_url); + configure_agent_command_environment(&mut command, home, executable)?; let output = command .output() .map_err(|error| format!("执行 Pi 插件安装失败: {error}"))?; @@ -613,6 +641,7 @@ pub(crate) fn update_pi_package( .stdin(Stdio::null()); configure_background_command(&mut command); configure_networked_command(&mut command, proxy_url); + configure_agent_command_environment(&mut command, home, executable)?; let output = command .output() .map_err(|error| format!("执行 Pi 插件更新失败: {error}"))?; @@ -644,6 +673,7 @@ pub(crate) fn remove_pi_package(executable: &Path, home: &Path) -> Result<(), St .current_dir(home) .stdin(Stdio::null()); configure_background_command(&mut command); + configure_agent_command_environment(&mut command, home, executable)?; let output = command .output() .map_err(|error| format!("鎵ц Pi 鎻掍欢鍗歌浇澶辫触: {error}"))?; @@ -899,9 +929,11 @@ pub(crate) fn inspect_agent_config( find_named_agent_executable(home, &["zcode"]) .filter(|path| executable.as_ref() != Some(path)) .as_deref() - .and_then(read_agent_version) + .and_then(|path| read_agent_version(path, home)) } else if should_probe_primary_agent_executable_version(client) { - executable.as_deref().and_then(read_agent_version) + executable + .as_deref() + .and_then(|path| read_agent_version(path, home)) } else { None }; @@ -915,7 +947,7 @@ pub(crate) fn inspect_agent_config( AgentClient::ClaudeDesktop => read_claude_desktop_version(home), AgentClient::Codex => codex_app_installation .as_ref() - .and_then(read_codex_app_installation_version), + .and_then(|installation| read_codex_app_installation_version(installation, home)), AgentClient::OpenCode => opencode_desktop_application .as_deref() .and_then(read_opencode_desktop_version), @@ -1950,7 +1982,10 @@ pub(crate) fn read_claude_desktop_version(home: &Path) -> Option { } } -pub(crate) fn read_codex_app_installation_version(installation: &CodexAppTarget) -> Option { +pub(crate) fn read_codex_app_installation_version( + installation: &CodexAppTarget, + _home: &Path, +) -> Option { #[cfg(target_os = "windows")] { match installation { @@ -1959,7 +1994,7 @@ pub(crate) fn read_codex_app_installation_version(installation: &CodexAppTarget) read_windows_codex_store_version() } CodexAppTarget::Application(path) => { - read_windows_executable_version(path).or_else(|| read_agent_version(path)) + read_windows_executable_version(path).or_else(|| read_agent_version(path, _home)) } } } @@ -1979,7 +2014,7 @@ pub(crate) fn read_zcode_app_version(home: &Path) -> Option { #[cfg(target_os = "windows")] { find_zcode_desktop_executable(home).and_then(|path| { - read_windows_executable_version(&path).or_else(|| read_agent_version(&path)) + read_windows_executable_version(&path).or_else(|| read_agent_version(&path, home)) }) } #[cfg(target_os = "macos")] @@ -1992,7 +2027,8 @@ pub(crate) fn read_zcode_app_version(home: &Path) -> Option { .find(|path| path.is_dir()) .and_then(|path| read_macos_app_version(&path)) .or_else(|| { - find_named_agent_executable(home, &["zcode"]).and_then(|path| read_agent_version(&path)) + find_named_agent_executable(home, &["zcode"]) + .and_then(|path| read_agent_version(&path, home)) }) } #[cfg(not(any(target_os = "windows", target_os = "macos")))] @@ -2748,7 +2784,7 @@ fn terminate_agent_probe_process(child: &mut Child) { let _ = child.wait(); } -pub(crate) fn read_agent_version(path: &Path) -> Option { +pub(crate) fn read_agent_version(path: &Path, home: &Path) -> Option { #[cfg(target_os = "windows")] let mut command = windows_command_for_executable(path, false); @@ -2757,6 +2793,7 @@ pub(crate) fn read_agent_version(path: &Path) -> Option { command.arg("--version"); configure_background_command(&mut command); + configure_agent_command_environment(&mut command, home, path).ok()?; let output = command_output_with_timeout(&mut command, AGENT_VERSION_PROBE_TIMEOUT).ok()??; if !output.status.success() { return None; diff --git a/src-tauri/src/tests/agent_configuration.rs b/src-tauri/src/tests/agent_configuration.rs index 844e810..5650c24 100644 --- a/src-tauri/src/tests/agent_configuration.rs +++ b/src-tauri/src/tests/agent_configuration.rs @@ -1919,6 +1919,67 @@ fn detected_agent_version_requires_a_real_version_value() { assert!(normalize_detected_agent_version(&"1".repeat(257)).is_none()); } +#[test] +fn agent_command_path_prioritizes_the_executable_and_preserves_discovered_paths() { + let home = agent_test_home("command-path"); + let executable_directory = home.join("node-runtime/bin"); + let executable = executable_directory.join("pi"); + fs::create_dir_all(&executable_directory).unwrap(); + + let path = agent_command_path(&home, &executable).unwrap(); + let directories = env::split_paths(&path).collect::>(); + + assert_eq!(directories.first(), Some(&executable_directory)); + assert!(directories.contains(&home.join(".local/bin"))); + if let Some(inherited_path) = env::var_os("PATH") { + for inherited_directory in env::split_paths(&inherited_path) { + assert!(directories.contains(&inherited_directory)); + } + } + + fs::remove_dir_all(home).unwrap(); +} + +#[test] +fn agent_version_probe_exposes_the_executable_directory_to_child_commands() { + let home = agent_test_home("version-path"); + let executable_directory = home.join("node-runtime/bin"); + fs::create_dir_all(&executable_directory).unwrap(); + + #[cfg(target_os = "windows")] + let (executable, helper) = ( + executable_directory.join("pi.cmd"), + executable_directory.join("agent-version-helper.cmd"), + ); + #[cfg(not(target_os = "windows"))] + let (executable, helper) = ( + executable_directory.join("pi"), + executable_directory.join("agent-version-helper"), + ); + + #[cfg(target_os = "windows")] + { + fs::write(&executable, "@echo off\r\nagent-version-helper.cmd\r\n").unwrap(); + fs::write(&helper, "@echo off\r\necho pi 1.2.3\r\n").unwrap(); + } + #[cfg(not(target_os = "windows"))] + { + use std::os::unix::fs::PermissionsExt; + + fs::write(&executable, "#!/usr/bin/env sh\nagent-version-helper\n").unwrap(); + fs::write(&helper, "#!/usr/bin/env sh\nprintf 'pi 1.2.3\\n'\n").unwrap(); + fs::set_permissions(&executable, fs::Permissions::from_mode(0o755)).unwrap(); + fs::set_permissions(&helper, fs::Permissions::from_mode(0o755)).unwrap(); + } + + assert_eq!( + read_agent_version(&executable, &home).as_deref(), + Some("pi 1.2.3") + ); + + fs::remove_dir_all(home).unwrap(); +} + #[test] fn agent_probe_command_captures_output_before_timeout() { #[cfg(target_os = "windows")]