Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion desktop/src-tauri/src/managed_agents/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub(crate) use login_shell::{
};
pub(crate) use presets::{
canonical_harness_command, command_for_runtime_id, preset_harness_definitions,
preset_harness_ids,
preset_harness_ids, preset_mcp_command_for,
};
use presets::{preset_catalog_entry, PRESET_HARNESSES};
pub(crate) use runtime_metadata::KnownAcpRuntime;
Expand Down Expand Up @@ -286,6 +286,16 @@ pub(crate) fn known_acp_runtime(command: &str) -> Option<&'static KnownAcpRuntim
})
}

/// Resolve the MCP sidecar command for an agent harness.
///
/// Builtins declare `mcp_command` on `KNOWN_ACP_RUNTIMES`. Presets are not in
/// that table, so fall through to `preset_mcp_command_for` (#7023).
pub(crate) fn resolve_mcp_command(command: &str) -> Option<&'static str> {
known_acp_runtime(command)
.and_then(|runtime| runtime.mcp_command)
.or_else(|| preset_mcp_command_for(command))
}

pub(crate) fn known_acp_runtime_exact(id: &str) -> Option<&'static KnownAcpRuntime> {
KNOWN_ACP_RUNTIMES.iter().find(|p| p.id == id)
}
Expand Down
50 changes: 49 additions & 1 deletion desktop/src-tauri/src/managed_agents/discovery/presets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ pub(super) fn preset_catalog_entry(
def.command,
def.args.iter().map(|arg| arg.to_string()).collect(),
),
mcp_command: None,
// Preset ACP harnesses publish replies through buzz-dev-mcp, same as
// the built-in runtimes that already carry an MCP command (#7023).
mcp_command: Some("buzz-dev-mcp".to_string()),
model_env_var: None,
provider_env_var: None,
thinking_env_var: None,
Expand Down Expand Up @@ -217,6 +219,20 @@ pub(super) fn preset_command_for_id(id: &str) -> Option<&'static str> {
.map(|p| p.command)
}

/// MCP command for a preset harness matched by command (or path basename).
///
/// Presets are absent from `KNOWN_ACP_RUNTIMES`, so spawn must consult this
/// when resolving `BUZZ_ACP_MCP_COMMAND` (#7023).
pub(crate) fn preset_mcp_command_for(command: &str) -> Option<&'static str> {
let normalized = super::normalize_command_identity(command);
PRESET_HARNESSES
.iter()
.any(|preset| {
super::normalize_command_identity(preset.command) == normalized || preset.id == normalized
})
.then_some("buzz-dev-mcp")
}

/// Return the primary harness command for a given runtime id, or `None`.
///
/// Checks static builtins, then the static preset list (always available,
Expand Down Expand Up @@ -317,6 +333,7 @@ mod tests {
assert_eq!(entry.binary_path.as_deref(), Some("/usr/local/bin/devin"));
assert_eq!(entry.auth_status, AuthStatus::NotApplicable);
assert_eq!(entry.source, HarnessSource::Preset);
assert_eq!(entry.mcp_command.as_deref(), Some("buzz-dev-mcp"));

let missing_entry = preset_catalog_entry(preset, |_| None);
assert_eq!(
Expand Down Expand Up @@ -470,4 +487,35 @@ mod tests {
"uncapped preset (devin) must have max_parallelism: None"
);
}

#[test]
fn preset_mcp_command_resolves_for_hermes_and_paths() {
assert_eq!(
super::preset_mcp_command_for("hermes-acp"),
Some("buzz-dev-mcp")
);
assert_eq!(
super::preset_mcp_command_for("/opt/homebrew/bin/hermes-acp"),
Some("buzz-dev-mcp")
);
assert_eq!(
super::preset_mcp_command_for("hermes"),
Some("buzz-dev-mcp")
);
assert_eq!(super::preset_mcp_command_for("totally-unknown-agent"), None);
}

#[test]
fn resolve_mcp_command_prefers_builtin_then_preset() {
assert_eq!(
crate::managed_agents::resolve_mcp_command("codex-acp"),
Some("buzz-dev-mcp")
);
assert_eq!(
crate::managed_agents::resolve_mcp_command("hermes-acp"),
Some("buzz-dev-mcp")
);
// Goose is a builtin without an MCP sidecar declaration.
assert_eq!(crate::managed_agents::resolve_mcp_command("goose"), None);
}
}
6 changes: 2 additions & 4 deletions desktop/src-tauri/src/managed_agents/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,7 @@ pub fn build_managed_agent_summary(
env: Default::default(),
}
});
let effective_mcp_command = known_acp_runtime(&descriptor.command)
.and_then(|r| r.mcp_command)
let effective_mcp_command = crate::managed_agents::resolve_mcp_command(&descriptor.command)
.unwrap_or("")
.to_string();

Expand Down Expand Up @@ -477,8 +476,7 @@ pub fn spawn_agent_child(
.map_err(|error| format!("failed to clone log handle: {error}"))?;
let resolved_acp_command = resolve_command(&record.acp_command)
.ok_or_else(|| missing_command_message(&record.acp_command, "ACP harness command"))?;
let effective_mcp_command = known_acp_runtime(effective_command)
.and_then(|r| r.mcp_command)
let effective_mcp_command = crate::managed_agents::resolve_mcp_command(effective_command)
.unwrap_or("");
let resolved_mcp_command: Option<std::path::PathBuf> = if effective_mcp_command.is_empty() {
None
Expand Down
3 changes: 1 addition & 2 deletions desktop/src-tauri/src/managed_agents/spawn_snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,7 @@ impl SpawnConfigSnapshot {
acp_command: record.acp_command.clone(),
command: descriptor.command.clone(),
args: descriptor.args.clone(),
mcp_command: known_acp_runtime(&descriptor.command)
.and_then(|runtime| runtime.mcp_command)
mcp_command: crate::managed_agents::resolve_mcp_command(&descriptor.command)
.unwrap_or("")
.to_string(),
// Effort has ONE representation in the snapshot: `effort_level`
Expand Down