mirror of
https://github.com/block/buzz.git
synced 2026-11-06 01:11:33 +00:00
On Windows, the model dropdown never populates for CLI harnesses (Claude Code, Codex) while the same flow works on macOS. Model discovery spawns `buzz-acp models` with `PATH` taken from `login_shell_path()` — which by design always returns `None` on Windows (Git Bash's POSIX-shaped PATH would poison native children). The discovery child therefore ran with only the raw inherited process PATH, missing the Buzz-managed Node/npm directories and exe-parent sidecar dir, so the ACP adapter's `.cmd` shims failed to resolve `node` and discovery returned nothing. macOS worked only because a login-shell PATH exists there. The fix reuses the existing `augmented_path()` helper (already used by CLI login probes and auth commands, built on the same `build_augmented_path` kernel as the real agent spawn), so model discovery resolves the identical toolchain the agent will actually run with. `login_shell_path()` remains the login-shell component inside that composition — on macOS the composed PATH is a superset of the previous value. Related: #2661 (managed Node fallback these entries point at), reported in the Windows install-issues follow-up. Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1fgdl5qqnh3k3f2xkqrvt7cujalhm623x4s7fdjdj5yrtp5fzjl9qrjpucw <4a1bfa0013bc6d14a8d600d8bf6392efefbd2a26ac3c96c9b2a106b0d12297ca@buzz.block.builderlab.xyz>
85 lines
3.7 KiB
Rust
85 lines
3.7 KiB
Rust
use std::{collections::BTreeMap, path::PathBuf};
|
|
|
|
use crate::managed_agents::{
|
|
build_buzz_agent_provider_defaults, default_agent_workdir, known_acp_runtime,
|
|
redact_env_values_in, AgentModelsResponse,
|
|
};
|
|
|
|
use super::agent_models::normalize_agent_models;
|
|
|
|
pub(super) async fn run_agent_models_command(
|
|
resolved_acp: PathBuf,
|
|
agent_command: String,
|
|
agent_args: Vec<String>,
|
|
persisted_model: Option<String>,
|
|
merged_env: BTreeMap<String, String>,
|
|
) -> Result<AgentModelsResponse, String> {
|
|
// Clone the env map for redaction below — `merged_env` is moved
|
|
// into the spawn_blocking closure and we still need the values to
|
|
// scrub any user-supplied secrets that the child surfaces in stderr.
|
|
let env_for_redaction = merged_env.clone();
|
|
|
|
// Use spawn_blocking because the desktop Tauri crate doesn't enable
|
|
// tokio's `process` feature. std::process::Command is synchronous
|
|
// but fine for a short-lived subprocess (~2-5s).
|
|
let output = tokio::task::spawn_blocking(move || {
|
|
let mut cmd = std::process::Command::new(&resolved_acp);
|
|
if let Some(home) = default_agent_workdir() {
|
|
cmd.current_dir(home);
|
|
}
|
|
// Inject the same augmented PATH used for launched agents and CLI
|
|
// probes: managed Node/npm dirs, exe-parent sidecars, login-shell
|
|
// PATH, and (Windows) the inherited process PATH. login_shell_path()
|
|
// alone is always None on Windows, which left this child with no
|
|
// managed Node dirs — the ACP adapter's `.cmd` shims then failed with
|
|
// `'node' is not recognized` and the model dropdown stayed empty.
|
|
if let Some(ref path) = crate::managed_agents::readiness::cli_probe::augmented_path() {
|
|
cmd.env("PATH", path);
|
|
}
|
|
cmd.arg("models")
|
|
.arg("--json")
|
|
.env("BUZZ_ACP_AGENT_COMMAND", &agent_command)
|
|
.env("BUZZ_ACP_AGENT_ARGS", agent_args.join(","));
|
|
if let Some(meta) = known_acp_runtime(&agent_command) {
|
|
for (key, value) in meta.default_env {
|
|
if std::env::var(key).is_err() {
|
|
cmd.env(key, value);
|
|
}
|
|
}
|
|
}
|
|
// Mirror runtime spawn: internal builds may bake provider/model
|
|
// defaults. User-provided env below still wins.
|
|
build_buzz_agent_provider_defaults(&mut cmd);
|
|
// User env layering — written LAST so it overrides any Buzz-set env above.
|
|
for (k, v) in &merged_env {
|
|
cmd.env(k, v);
|
|
}
|
|
crate::managed_agents::configure_runtime_cli(&mut cmd, known_acp_runtime(&agent_command));
|
|
crate::util::configure_no_window(&mut cmd);
|
|
cmd.stdout(std::process::Stdio::piped())
|
|
.stderr(std::process::Stdio::piped())
|
|
.output()
|
|
.map_err(|e| format!("failed to spawn buzz-acp models: {e}"))
|
|
})
|
|
.await
|
|
.map_err(|e| format!("model discovery task failed: {e}"))?
|
|
.map_err(|e: String| e)?;
|
|
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
// Scrub any user-supplied env values before surfacing stderr to
|
|
// the frontend — persona/agent env_vars may carry API keys that
|
|
// a failing child process echoed back.
|
|
let stderr_redacted = redact_env_values_in(stderr.as_ref(), &env_for_redaction);
|
|
return Err(format!(
|
|
"buzz-acp models failed (exit {}): {stderr_redacted}",
|
|
output.status.code().unwrap_or(-1)
|
|
));
|
|
}
|
|
|
|
let raw: serde_json::Value = serde_json::from_slice(&output.stdout)
|
|
.map_err(|e| format!("failed to parse model JSON: {e}"))?;
|
|
|
|
Ok(normalize_agent_models(&raw, persisted_model))
|
|
}
|