mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
fix(desktop): resolve agent command path for DMG builds (#372)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -45,7 +45,7 @@ const overrides = new Map([
|
||||
["src-tauri/src/lib.rs", 710], // sprout-media:// proxy + Range headers + Sprout nest init (ensure_nest) in setup() + huddle command registration + PTT global shortcut handler + persona pack commands + app_handle storage for event emission
|
||||
["src-tauri/src/commands/media.rs", 720], // ffmpeg video transcode + poster frame extraction + run_ffmpeg_with_timeout (find_ffmpeg, is_video_file, transcode_to_mp4, extract_poster_frame, transcode_and_extract_poster) + spawn_blocking wrappers + tests
|
||||
["src-tauri/src/commands/agents.rs", 880], // remote agent lifecycle routing (local + provider branches) + scope enforcement + persona pack metadata wiring + mcp_toolsets field
|
||||
["src-tauri/src/managed_agents/runtime.rs", 690], // KNOWN_AGENT_BINARIES const + process_belongs_to_us FFI (macOS proc_name + Linux /proc/comm) + terminate_process + start/stop/sync lifecycle + pack persona live-read
|
||||
["src-tauri/src/managed_agents/runtime.rs", 700], // KNOWN_AGENT_BINARIES const + process_belongs_to_us FFI (macOS proc_name + Linux /proc/comm) + terminate_process + start/stop/sync lifecycle + pack persona live-read + login shell PATH augmentation
|
||||
["src-tauri/src/managed_agents/backend.rs", 530], // provider IPC, validation, discovery, binary resolution + tests
|
||||
["src/features/huddle/HuddleContext.tsx", 650], // huddle lifecycle context + joinHuddle + connectAndSetupMedia shared helper + activeSpeakers/isReconnecting state + PTT (reusable AudioContext) + TTS subscription + mic level analyser (10fps throttle) + agent pubkey refresh
|
||||
["src/features/agents/hooks.ts", 540], // agent query/mutation surface now includes built-in persona library activation + useUpdateManagedAgentMutation
|
||||
|
||||
@@ -49,12 +49,11 @@ pub async fn get_agent_models(
|
||||
|
||||
let args = normalize_agent_args(&record.agent_command, record.agent_args.clone());
|
||||
|
||||
(
|
||||
resolved,
|
||||
record.agent_command.clone(),
|
||||
args,
|
||||
record.model.clone(),
|
||||
)
|
||||
let resolved_agent = resolve_command(&record.agent_command, Some(&app))
|
||||
.map(|p| p.display().to_string())
|
||||
.unwrap_or_else(|| record.agent_command.clone());
|
||||
|
||||
(resolved, resolved_agent, args, record.model.clone())
|
||||
}; // store lock released — subprocess runs without holding the lock
|
||||
|
||||
// Use spawn_blocking because the desktop Tauri crate doesn't enable
|
||||
@@ -65,6 +64,9 @@ pub async fn get_agent_models(
|
||||
if let Some(home) = default_agent_workdir() {
|
||||
cmd.current_dir(home);
|
||||
}
|
||||
if let Some(ref path) = crate::managed_agents::login_shell_path() {
|
||||
cmd.env("PATH", path);
|
||||
}
|
||||
cmd.arg("models")
|
||||
.arg("--json")
|
||||
.env("SPROUT_ACP_AGENT_COMMAND", &agent_command)
|
||||
|
||||
@@ -232,32 +232,45 @@ fn path_candidates_from_env(command: &str) -> Vec<PathBuf> {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn find_via_login_shell(command: &str) -> Option<PathBuf> {
|
||||
/// Run a command in a login shell (tries zsh then bash).
|
||||
/// Returns trimmed stdout if the command succeeds with non-empty output.
|
||||
fn run_in_login_shell(args: &[&str]) -> Option<String> {
|
||||
for shell in ["/bin/zsh", "/bin/bash"] {
|
||||
let Ok(output) = Command::new(shell)
|
||||
.args(["-l", "-c", r#"command -v -- "$1""#, "_", command])
|
||||
.output()
|
||||
else {
|
||||
let Ok(output) = Command::new(shell).args(args).output() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !output.status.success() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let Some(resolved) = stdout.lines().rfind(|line| !line.trim().is_empty()) else {
|
||||
continue;
|
||||
};
|
||||
let path = PathBuf::from(resolved.trim());
|
||||
if path.is_absolute() && path.exists() {
|
||||
return Some(path);
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
if !stdout.is_empty() {
|
||||
return Some(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn find_via_login_shell(command: &str) -> Option<PathBuf> {
|
||||
let stdout = run_in_login_shell(&["-l", "-c", r#"command -v -- "$1""#, "_", command])?;
|
||||
let resolved = stdout.lines().rfind(|line| !line.trim().is_empty())?;
|
||||
let path = PathBuf::from(resolved.trim());
|
||||
(path.is_absolute() && path.exists()).then_some(path)
|
||||
}
|
||||
|
||||
/// Return the user's full PATH from a login shell.
|
||||
/// Cached via OnceLock so we only spawn one shell per app lifetime.
|
||||
pub fn login_shell_path() -> Option<String> {
|
||||
use std::sync::OnceLock;
|
||||
static CACHED: OnceLock<Option<String>> = OnceLock::new();
|
||||
CACHED
|
||||
.get_or_init(|| {
|
||||
let stdout = run_in_login_shell(&["-l", "-c", "echo $PATH"])?;
|
||||
let last_line = stdout.lines().rfind(|l| !l.trim().is_empty())?;
|
||||
Some(last_line.trim().to_string())
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
|
||||
fn find_command(command: &str) -> Option<PathBuf> {
|
||||
resolve_command(command, None)
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ use tauri::AppHandle;
|
||||
|
||||
use crate::{
|
||||
managed_agents::{
|
||||
append_log_marker, managed_agent_log_path, missing_command_message, normalize_agent_args,
|
||||
open_log_file, resolve_command, ManagedAgentProcess, ManagedAgentRecord,
|
||||
ManagedAgentSummary,
|
||||
append_log_marker, login_shell_path, managed_agent_log_path, missing_command_message,
|
||||
normalize_agent_args, open_log_file, resolve_command, ManagedAgentProcess,
|
||||
ManagedAgentRecord, ManagedAgentSummary,
|
||||
},
|
||||
util::now_iso,
|
||||
};
|
||||
@@ -482,6 +482,13 @@ pub fn start_managed_agent_process(
|
||||
.ok_or_else(|| missing_command_message(&record.acp_command, "ACP harness command"))?;
|
||||
let resolved_mcp_command = resolve_command(&record.mcp_command, Some(app))
|
||||
.ok_or_else(|| missing_command_message(&record.mcp_command, "MCP server command"))?;
|
||||
// Resolve agent command to a full path (DMG launches have minimal PATH).
|
||||
let resolved_agent_command = resolve_command(&record.agent_command, Some(app))
|
||||
.map(|p| p.display().to_string())
|
||||
.unwrap_or_else(|| record.agent_command.clone());
|
||||
|
||||
// Augment PATH for DMG launches so child processes (e.g. #!/usr/bin/env node) can find their runtimes.
|
||||
let augmented_path = login_shell_path();
|
||||
|
||||
let mut command = std::process::Command::new(&resolved_acp_command);
|
||||
if let Some(home) = super::default_agent_workdir() {
|
||||
@@ -490,9 +497,12 @@ pub fn start_managed_agent_process(
|
||||
command.stdin(std::process::Stdio::null());
|
||||
command.stdout(std::process::Stdio::from(stdout));
|
||||
command.stderr(std::process::Stdio::from(stderr));
|
||||
if let Some(ref path) = augmented_path {
|
||||
command.env("PATH", path);
|
||||
}
|
||||
command.env("SPROUT_PRIVATE_KEY", &record.private_key_nsec);
|
||||
command.env("SPROUT_RELAY_URL", &record.relay_url);
|
||||
command.env("SPROUT_ACP_AGENT_COMMAND", &record.agent_command);
|
||||
command.env("SPROUT_ACP_AGENT_COMMAND", &resolved_agent_command);
|
||||
command.env("SPROUT_ACP_AGENT_ARGS", agent_args.join(","));
|
||||
command.env("SPROUT_ACP_MCP_COMMAND", &resolved_mcp_command);
|
||||
// Desktop-managed agents should favor the latest owner mention in a
|
||||
|
||||
Reference in New Issue
Block a user