From f40c010530e85a4240794fc35ee6feb46c74fb7b Mon Sep 17 00:00:00 2001 From: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co> Date: Thu, 4 Jun 2026 10:55:11 -0400 Subject: [PATCH] fix(mesh): run relay-mesh agents on sprout-agent, not goose The "Run on relay mesh" preset handed the agent the global default runtime (goose) while setting SPROUT_AGENT_PROVIDER + OPENAI_COMPAT_* env vars. Goose ignores those and falls back to its own configured provider, so mesh agents never used the local mesh endpoint. Source the agent command and MCP from the sprout-agent provider catalog entry, which reads those env vars (crates/sprout-agent/src/config.rs). This matches the frontend preset contract (agentCommand: sprout-agent, mcpCommand: sprout-dev-mcp) the JS test fixture already documented. Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co> --- desktop/src-tauri/src/mesh_llm/mod.rs | 57 +++-------------- desktop/src-tauri/src/mesh_llm/mod_tests.rs | 32 ++++++++++ desktop/src-tauri/src/mesh_llm/preset.rs | 69 +++++++++++++++++++++ 3 files changed, 110 insertions(+), 48 deletions(-) create mode 100644 desktop/src-tauri/src/mesh_llm/preset.rs diff --git a/desktop/src-tauri/src/mesh_llm/mod.rs b/desktop/src-tauri/src/mesh_llm/mod.rs index 3eb3670103..d162824dd9 100644 --- a/desktop/src-tauri/src/mesh_llm/mod.rs +++ b/desktop/src-tauri/src/mesh_llm/mod.rs @@ -4,6 +4,9 @@ mod discovery; pub use discovery::{availability_from_events, mesh_status_filter}; use discovery::{device_name_from_status, endpoint_id_from_status, enrich_status_payload_identity}; +mod preset; +pub use preset::{agent_preset, MeshAgentPreset, MeshAgentPresetRequest}; + use mesh_llm_sdk::{client, serve, EmbeddedNodeHandle, MeshDiscoveryMode}; use serde::{Deserialize, Serialize}; @@ -13,6 +16,12 @@ const MESH_STATUS_KIND: u64 = 30_621; const MESH_API_PORT_ENV: &str = "SPROUT_MESH_API_PORT"; const MESH_CONSOLE_PORT_ENV: &str = "SPROUT_MESH_CONSOLE_PORT"; const RELAY_MESH_API_KEY_PLACEHOLDER: &str = "sprout-mesh-local"; +/// ACP provider relay-mesh agents run on. Sources of truth for its command + +/// MCP live in the provider catalog (`known_acp_provider_exact`); these are +/// only the fallbacks. `sprout-agent` reads the `SPROUT_AGENT_PROVIDER` / +/// `OPENAI_COMPAT_*` env vars below — goose (the global default) does not. +const MESH_AGENT_PROVIDER_ID: &str = "sprout-agent"; +const MESH_AGENT_MCP_COMMAND: &str = "sprout-dev-mcp"; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] @@ -442,54 +451,6 @@ pub(super) fn dedupe_models(models: Vec) -> Vec, - pub mcp_command: String, - pub model: String, - pub env_vars: BTreeMap, -} - -pub fn agent_preset(request: MeshAgentPresetRequest) -> Result { - let model = request.model_id.trim(); - if model.is_empty() { - return Err("modelId is required".to_string()); - } - Ok(MeshAgentPreset { - provider_id: "relay-mesh".to_string(), - label: "Relay mesh".to_string(), - acp_command: crate::managed_agents::DEFAULT_ACP_COMMAND.to_string(), - agent_command: crate::managed_agents::DEFAULT_AGENT_COMMAND.to_string(), - agent_args: Vec::new(), - mcp_command: crate::managed_agents::DEFAULT_MCP_COMMAND.to_string(), - model: model.to_string(), - env_vars: BTreeMap::from([ - ("SPROUT_AGENT_PROVIDER".to_string(), "openai".to_string()), - ( - "OPENAI_COMPAT_BASE_URL".to_string(), - relay_mesh_api_base_url()?, - ), - ("OPENAI_COMPAT_MODEL".to_string(), model.to_string()), - ( - "OPENAI_COMPAT_API_KEY".to_string(), - RELAY_MESH_API_KEY_PLACEHOLDER.to_string(), - ), - ("OPENAI_COMPAT_API".to_string(), "chat".to_string()), - ]), - }) -} - #[cfg(test)] #[path = "mod_tests.rs"] mod mod_tests; diff --git a/desktop/src-tauri/src/mesh_llm/mod_tests.rs b/desktop/src-tauri/src/mesh_llm/mod_tests.rs index 0335d3373f..b5e13cb43a 100644 --- a/desktop/src-tauri/src/mesh_llm/mod_tests.rs +++ b/desktop/src-tauri/src/mesh_llm/mod_tests.rs @@ -32,3 +32,35 @@ fn model_ref_is_family_agnostic() { assert!(!looks_like_model_ref("Qwen3-35B")); assert!(!looks_like_model_ref("")); } + +#[test] +fn agent_preset_runs_on_sprout_agent_not_goose() { + // Regression (Tyler): the relay-mesh preset used to hand the agent the + // global default runtime (goose), which ignores the OpenAI-compat env + // vars and falls back to its own provider. Mesh agents must run on + // sprout-agent, which reads those vars. + let preset = super::agent_preset(super::MeshAgentPresetRequest { + model_id: "Qwen3-8B-Q4_K_M".to_string(), + }) + .expect("preset for a valid model id"); + + assert_eq!(preset.agent_command, "sprout-agent"); + assert_ne!(preset.agent_command, "goose"); + assert_eq!(preset.mcp_command, "sprout-dev-mcp"); + + // The env vars sprout-agent's config layer reads (crates/sprout-agent). + assert_eq!( + preset + .env_vars + .get("SPROUT_AGENT_PROVIDER") + .map(String::as_str), + Some("openai") + ); + assert_eq!( + preset + .env_vars + .get("OPENAI_COMPAT_MODEL") + .map(String::as_str), + Some("Qwen3-8B-Q4_K_M") + ); +} diff --git a/desktop/src-tauri/src/mesh_llm/preset.rs b/desktop/src-tauri/src/mesh_llm/preset.rs new file mode 100644 index 0000000000..8069cc9915 --- /dev/null +++ b/desktop/src-tauri/src/mesh_llm/preset.rs @@ -0,0 +1,69 @@ +//! Relay-mesh "Run on relay mesh" agent preset. Kept in a sibling file so +//! `mod.rs` stays under the 500-line budget; `#[path]`-included from there. +use std::collections::BTreeMap; + +use serde::{Deserialize, Serialize}; + +use super::{ + relay_mesh_api_base_url, MESH_AGENT_MCP_COMMAND, MESH_AGENT_PROVIDER_ID, + RELAY_MESH_API_KEY_PLACEHOLDER, +}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct MeshAgentPresetRequest { + pub model_id: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct MeshAgentPreset { + pub provider_id: String, + pub label: String, + pub acp_command: String, + pub agent_command: String, + pub agent_args: Vec, + pub mcp_command: String, + pub model: String, + pub env_vars: BTreeMap, +} + +pub fn agent_preset(request: MeshAgentPresetRequest) -> Result { + let model = request.model_id.trim(); + if model.is_empty() { + return Err("modelId is required".to_string()); + } + // Run on sprout-agent, not the global default (goose). Source command + + // MCP from the catalog so this can't drift from the provider definition. + let sprout_agent = crate::managed_agents::known_acp_provider_exact(MESH_AGENT_PROVIDER_ID); + let agent_command = sprout_agent + .and_then(|p| p.commands.first().copied()) + .unwrap_or(MESH_AGENT_PROVIDER_ID) + .to_string(); + let mcp_command = sprout_agent + .and_then(|p| p.mcp_command) + .unwrap_or(MESH_AGENT_MCP_COMMAND) + .to_string(); + Ok(MeshAgentPreset { + provider_id: "relay-mesh".to_string(), + label: "Relay mesh".to_string(), + acp_command: crate::managed_agents::DEFAULT_ACP_COMMAND.to_string(), + agent_command, + agent_args: Vec::new(), + mcp_command, + model: model.to_string(), + env_vars: BTreeMap::from([ + ("SPROUT_AGENT_PROVIDER".to_string(), "openai".to_string()), + ( + "OPENAI_COMPAT_BASE_URL".to_string(), + relay_mesh_api_base_url()?, + ), + ("OPENAI_COMPAT_MODEL".to_string(), model.to_string()), + ( + "OPENAI_COMPAT_API_KEY".to_string(), + RELAY_MESH_API_KEY_PLACEHOLDER.to_string(), + ), + ("OPENAI_COMPAT_API".to_string(), "chat".to_string()), + ]), + }) +}