Skip to content
4 changes: 3 additions & 1 deletion desktop/scripts/check-file-sizes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,9 @@ const overrides = new Map([
// route PowerShell CLI installs natively on Windows (bypasses Git Bash PATH
// poisoning that resolved GNU tar instead of bsdtar → Codex install failure).
// Includes unit tests for detection, routing, and -Command body preservation.
["src-tauri/src/commands/agent_discovery.rs", 1810],
// +16: test_powershell_command_goose_catalog_dequoted proves the \$→$ escape
// fix for the Goose Windows installer (PR #2680 interaction with #2750).
["src-tauri/src/commands/agent_discovery.rs", 1826],
// draft-persistence predicate: submit-time `loadDraft` check + inline comment
// + deps-array entry in submitMessage closes the never-persisted-boundary
// defect (Thufir Pass-3 finding). Load-bearing correctness fix; queued to
Expand Down
9 changes: 4 additions & 5 deletions desktop/src-tauri/src/commands/agent_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -596,8 +596,6 @@ fn parse_models(raw: Option<&serde_json::Value>) -> (Vec<AcpModelEntry>, Option<

#[cfg(test)]
mod tests {
use std::collections::BTreeMap;

use super::*;
use crate::managed_agents::{BackendKind, RespondTo};

Expand All @@ -614,7 +612,8 @@ mod tests {
cli_install_commands: &[],
cli_install_commands_windows: &[],
adapter_install_commands: &[],
install_instructions_url: "",
cli_install_instructions_url: "",
adapter_install_instructions_url: "",
cli_install_hint: "",
adapter_install_hint: "",
skill_dir: None,
Expand Down Expand Up @@ -654,7 +653,7 @@ mod tests {
parallelism: 1,
system_prompt: None,
model: None,
env_vars: BTreeMap::new(),
env_vars: Default::default(),
start_on_app_launch: false,
auto_restart_on_config_change: true,
runtime_pid: None,
Expand Down Expand Up @@ -705,7 +704,7 @@ mod tests {
is_active: true,
source_team: None,
source_team_persona_slug: None,
env_vars: BTreeMap::new(),
env_vars: Default::default(),
respond_to: None,
respond_to_allowlist: Vec::new(),
parallelism: None,
Expand Down
44 changes: 30 additions & 14 deletions desktop/src-tauri/src/commands/agent_discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use crate::{
relay::query_relay,
};

mod post_install_verification;

fn active_installs() -> &'static std::sync::Mutex<std::collections::HashSet<String>> {
use std::collections::HashSet;
use std::sync::{Mutex, OnceLock};
Expand Down Expand Up @@ -229,11 +231,10 @@ fn install_acp_runtime_blocking(runtime_id: &str) -> Result<InstallRuntimeResult
}
}

// Clear the resolve cache so the next discovery picks up new binaries.
crate::managed_agents::clear_resolve_cache();
post_install_verification::run(runtime_id, &mut steps);

Ok(InstallRuntimeResult {
success: true,
success: steps.iter().all(|step| step.success),
steps,
restarted_count: 0,
failed_restart_count: 0,
Expand Down Expand Up @@ -1500,17 +1501,6 @@ mod tests {
);
}

/// Goose install commands are the same on all platforms (script is Windows-aware).
#[test]
fn test_goose_install_commands_same_on_all_platforms() {
let goose = crate::managed_agents::known_acp_runtime_exact("goose").unwrap();
assert_eq!(
goose.cli_install_commands_for_os(),
goose.cli_install_commands,
"goose install commands must be identical across platforms"
);
}

/// buzz-agent has no install commands on any platform.
#[test]
fn test_buzz_agent_has_no_install_commands() {
Expand Down Expand Up @@ -1675,6 +1665,32 @@ mod tests {
);
}

/// Goose Windows catalog command (discovery.rs:78) must dequote to a bare pipeline
/// with a literal `$env:` prefix — no backslash before the dollar sign.
/// This proves the `\$` → `$` escape fix: post-#2750 the spawn is native and
/// PowerShell receives the body verbatim, so a residual `\` would produce
/// `\$env:CONFIGURE='false'` which is a malformed statement.
#[cfg(windows)]
#[test]
fn test_powershell_command_goose_catalog_dequoted() {
let cmd = super::install_powershell_command(
r#"powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "$env:CONFIGURE='false'; irm https://raw.githubusercontent.com/aaif-goose/goose/main/download_cli.ps1 | iex""#,
);
assert_eq!(
cmd.get_args()
.map(|a| a.to_string_lossy().into_owned())
.collect::<Vec<_>>(),
vec![
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-Command",
"$env:CONFIGURE='false'; irm https://raw.githubusercontent.com/aaif-goose/goose/main/download_cli.ps1 | iex",
],
"Goose catalog command must dequote with bare $env: (no backslash before $)"
);
}

// ── install retry ─────────────────────────────────────────────────────────

/// Build an `InstallStepResult` with just the fields the retry loop reads.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use crate::managed_agents::{AcpAvailabilityStatus, InstallStepResult};

pub(super) fn run(runtime_id: &str, steps: &mut Vec<InstallStepResult>) {
// Observe PATH changes and binaries added after Buzz launched.
crate::managed_agents::refresh_login_shell_path();
crate::managed_agents::clear_resolve_cache();

let availability = crate::managed_agents::discover_acp_runtime_availability(runtime_id);
if let Some(failure) = failure(runtime_id, availability) {
steps.push(failure);
}
}

fn failure(
runtime_id: &str,
availability: Option<AcpAvailabilityStatus>,
) -> Option<InstallStepResult> {
if availability == Some(AcpAvailabilityStatus::Available) {
return None;
}

let observed = availability
.map(availability_label)
.unwrap_or_else(|| "missing from the runtime catalog".to_string());
Some(InstallStepResult {
step: "verify".to_string(),
command: format!("discover {runtime_id}"),
success: false,
stdout: String::new(),
stderr: format!(
"The installer finished, but Buzz still could not use {runtime_id} (observed: {observed})."
),
exit_code: None,
hint: Some(
"Buzz requires the vendor CLI executable, not only its desktop app. If the CLI was installed while Buzz was open, restart Buzz and check again."
.to_string(),
),
})
}

fn availability_label(status: AcpAvailabilityStatus) -> String {
match status {
AcpAvailabilityStatus::Available => "available",
AcpAvailabilityStatus::AdapterMissing => "ACP adapter missing",
AcpAvailabilityStatus::AdapterOutdated => "ACP adapter outdated",
AcpAvailabilityStatus::CliMissing => "CLI missing",
AcpAvailabilityStatus::NotInstalled => "not installed",
}
.to_string()
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn accepts_available_runtime() {
assert!(failure("goose", Some(AcpAvailabilityStatus::Available)).is_none());
}

#[test]
fn rejects_unresolved_runtime() {
let failure = failure("goose", Some(AcpAvailabilityStatus::NotInstalled))
.expect("not-installed runtime must fail verification");

assert_eq!(failure.step, "verify");
assert!(!failure.success);
assert!(failure.stderr.contains("observed: not installed"));
assert!(failure
.hint
.as_deref()
.is_some_and(|hint| hint.contains("desktop app")));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ fn test_runtime() -> &'static KnownAcpRuntime {
cli_install_commands: &[],
cli_install_commands_windows: &[],
adapter_install_commands: &[],
install_instructions_url: "",
cli_install_instructions_url: "",
adapter_install_instructions_url: "",
cli_install_hint: "",
adapter_install_hint: "",
skill_dir: None,
Expand Down Expand Up @@ -615,7 +616,8 @@ fn buzz_agent_runtime() -> &'static KnownAcpRuntime {
cli_install_commands: &[],
cli_install_commands_windows: &[],
adapter_install_commands: &[],
install_instructions_url: "",
cli_install_instructions_url: "",
adapter_install_instructions_url: "",
cli_install_hint: "",
adapter_install_hint: "",
skill_dir: None,
Expand Down
Loading
Loading