diff --git a/src-tauri/src/commands/acp.rs b/src-tauri/src/commands/acp.rs index ae6231e..145b9d9 100644 --- a/src-tauri/src/commands/acp.rs +++ b/src-tauri/src/commands/acp.rs @@ -85,15 +85,45 @@ fn is_cmd_available(cmd: &str) -> bool { which::which(cmd).is_ok() } +/// Detect the actual installed version of an npm global package by running +/// `npm list -g --json` and parsing the JSON output. +async fn detect_npm_global_version(package_name: &str) -> Option { + let npm_path = which::which("npm").ok()?; + let output = crate::process::tokio_command(npm_path) + .arg("list") + .arg("-g") + .arg(package_name) + .arg("--json") + .arg("--depth=0") + .output() + .await + .ok()?; + // npm list --json may exit non-zero when package is missing, but still + // outputs valid JSON with an empty dependencies object. + let stdout = String::from_utf8_lossy(&output.stdout); + let json: serde_json::Value = serde_json::from_str(&stdout).ok()?; + let version = json + .get("dependencies")? + .get(package_name)? + .get("version")? + .as_str()?; + normalize_version_candidate(version) +} + async fn detect_local_version(agent_type: AgentType) -> Option { let meta = registry::get_agent_meta(agent_type); match meta.distribution { registry::AgentDistribution::Npx { cmd, package, .. } => { - if is_cmd_available(cmd) { - version_from_package_spec(package) - } else { - None + if !is_cmd_available(cmd) { + return None; } + // Try `npm list -g --json` to get the real installed version. + let pkg_name = package_name_from_spec(package); + if let Some(v) = detect_npm_global_version(&pkg_name).await { + return Some(v); + } + // Fallback: parse version from registry package spec + version_from_package_spec(package) } registry::AgentDistribution::Binary { cmd, .. } => { binary_cache::detect_installed_version(agent_type, cmd)