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
2 changes: 1 addition & 1 deletion desktop/src-tauri/src/managed_agents/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use crate::managed_agents::{
mod presets;
mod runtime_metadata;

pub(crate) use presets::{preset_args_for_command, preset_harness_definitions, preset_harness_ids};
use presets::{preset_catalog_entry, PRESET_HARNESSES};
pub(crate) use presets::{preset_harness_definitions, preset_harness_ids};
pub(crate) use runtime_metadata::KnownAcpRuntime;

const GOOSE_AVATAR_URL: &str = "https://goose-docs.ai/img/logo_dark.png";
Expand Down
21 changes: 20 additions & 1 deletion desktop/src-tauri/src/managed_agents/discovery/presets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::managed_agents::{
AcpAvailabilityStatus, AcpRuntimeCatalogEntry, AuthStatus, HarnessSource,
};

use super::normalize_agent_args;
use super::{normalize_agent_args, normalize_command_identity};

/// Static data for a well-known tier-2 ACP harness.
pub(super) struct PresetHarness {
Expand Down Expand Up @@ -199,6 +199,25 @@ pub(crate) fn preset_harness_ids() -> &'static [&'static str] {
.as_slice()
}

/// Launch args for the [`PRESET_HARNESSES`] entry whose command matches the
/// given command, when an agent has a command but no resolvable runtime preset
/// (e.g. an explicit `agent_command_override` like `omp` with no `runtime` id).
///
/// Single source of truth: per-runtime launch args live only in
/// `PRESET_HARNESSES`, mirroring [`preset_harness_ids`], so every preset — omp,
/// grok, opencode, kimi, cursor, openclaw, … — is covered at the source instead
/// of being hand-mirrored. Without this, an agent pinned to e.g. `omp` with no
/// runtime id launches the bare command, which under headless Buzz (no
/// controlling TTY) drops into the interactive TUI instead of `omp acp` and the
/// ACP `initialize` handshake times out.
pub(crate) fn preset_args_for_command(command: &str) -> Option<Vec<String>> {
let identity = normalize_command_identity(command);
PRESET_HARNESSES
.iter()
.find(|preset| normalize_command_identity(preset.command) == identity)
.map(|preset| preset.args.iter().map(|arg| arg.to_string()).collect())
}

#[cfg(test)]
mod tests {
use std::path::PathBuf;
Expand Down
107 changes: 107 additions & 0 deletions desktop/src-tauri/src/managed_agents/readiness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,15 @@ pub(crate) fn resolve_effective_harness_descriptor(
normalize_agent_args(&effective_command, record_args)
} else if let Some(ref def) = harness_def {
normalize_agent_args(&effective_command, def.args.clone())
} else if let Some(preset_args) =
crate::managed_agents::discovery::preset_args_for_command(&effective_command)
{
// No runtime id resolved a harness preset, but the command itself
// is a known ACP runtime (e.g. an explicit override like `omp`).
// Use that preset's launch args so we start its ACP/stdio mode
// rather than the bare command, which would launch the interactive
// TUI and time out at ACP `initialize` under headless Buzz.
normalize_agent_args(&effective_command, preset_args)
} else {
normalize_agent_args(&effective_command, record_args)
}
Expand Down Expand Up @@ -1548,6 +1557,104 @@ mod tests {
);
}

#[test]
fn resolve_effective_harness_descriptor_uses_preset_args_for_pinned_command() {
// An agent pinned to a known ACP runtime via agent_command_override,
// with no runtime id and no instance args, must take the launch args
// from the matching PRESET_HARNESSES entry. Otherwise the bare command
// (e.g. `omp`) launches the interactive TUI, which times out at ACP
// `initialize` under headless Buzz (no controlling TTY).
let mk = |command: &str| crate::managed_agents::types::ManagedAgentRecord {
pubkey: "k".to_string(),
name: "agent".to_string(),
agent_command_override: Some(command.to_string()),
agent_args: vec![],
runtime: None,
persona_id: None,
private_key_nsec: String::new(),
auth_tag: None,
relay_url: String::new(),
avatar_url: None,
acp_command: "buzz-acp".to_string(),
agent_command: String::new(),
mcp_command: String::new(),
turn_timeout_seconds: 320,
idle_timeout_seconds: None,
max_turn_duration_seconds: None,
parallelism: 1,
system_prompt: None,
model: None,
provider: None,
persona_source_version: None,
env_vars: BTreeMap::new(),
start_on_app_launch: false,
auto_restart_on_config_change: true,
runtime_pid: None,
backend: Default::default(),
backend_agent_id: None,
provider_binary_path: None,
team_id: None,
persona_team_dir: None,
persona_name_in_team: None,
created_at: String::new(),
updated_at: String::new(),
last_started_at: None,
last_stopped_at: None,
last_exit_code: None,
last_error: None,
last_error_code: None,
respond_to: Default::default(),
respond_to_allowlist: vec![],
display_name: None,
slug: None,
name_pool: Vec::new(),
is_builtin: false,
is_active: true,
shared: false,
source_team: None,
source_team_persona_slug: None,
catalog_source: None,
definition_respond_to: None,
definition_respond_to_allowlist: Vec::new(),
definition_parallelism: None,
relay_mesh: None,
};
let global = crate::managed_agents::GlobalAgentConfig::default();

let desc = |command: &str| {
resolve_effective_harness_descriptor(&mk(command), &[], &global).unwrap()
};

// ACP-mode runtimes resolve to `acp`.
assert_eq!(desc("devin").args, vec!["acp".to_string()]);
assert_eq!(desc("omp").args, vec!["acp".to_string()]);
assert_eq!(desc("opencode").args, vec!["acp".to_string()]);
assert_eq!(desc("kimi").args, vec!["acp".to_string()]);
assert_eq!(desc("cursor-agent").args, vec!["acp".to_string()]);
assert_eq!(desc("openclaw").args, vec!["acp".to_string()]);
// Grok launches its agent stdio mode (not the TUI).
assert_eq!(
desc("grok").args,
vec![
"agent".to_string(),
"--always-approve".to_string(),
"stdio".to_string()
]
);

// Explicit instance args always win over the preset fallback.
let mut explicit = mk("omp");
explicit.agent_args = vec!["acp".to_string(), "--foo".to_string()];
let explicit_desc = resolve_effective_harness_descriptor(&explicit, &[], &global).unwrap();
assert_eq!(
explicit_desc.args,
vec!["acp".to_string(), "--foo".to_string()]
);

// An unknown command with no preset keeps the (empty) instance args.
assert!(desc("my-fancy-cli").args.is_empty());
}

// ── provider-specific model fallback tests ────────────────────────────

#[test]
Expand Down
7 changes: 7 additions & 0 deletions desktop/src-tauri/src/managed_agents/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,13 @@ pub fn spawn_agent_child(
let resolved_agent_command = resolve_command(effective_command)
.map(|p| p.display().to_string())
.unwrap_or_else(|| effective_command.clone());
append_log_marker(
&log_path,
&format!(
"resolved launch: agent_command={:?} args={:?}",
resolved_agent_command, agent_args
),
)?;

// The caller supplies the explicit canonical pair relay. This is the only
// relay this child may connect to, regardless of the record/workspace default.
Expand Down