refactor(desktop): feature-gate mesh-llm-sdk behind optional Cargo feature (#823)

This commit is contained in:
Will Pfleger
2026-06-02 20:25:01 +00:00
committed by GitHub
parent b72eee365f
commit fb514c8918
10 changed files with 118 additions and 36 deletions
+3 -3
View File
@@ -51,10 +51,10 @@ const overrides = new Map([
["src/shared/api/relayClientSession.ts", 1040], // durable websocket session manager with reconnect/replay/recovery state + sendTypingIndicator + fetchChannelHistoryBefore + subscribeToChannelLive (huddle TTS) + subscribeToHuddleEvents (huddle indicator) + disconnect() for workspace switch teardown + fetchEvents/subscribeLive/publishEvent for NIP-RS read state + publishUserStatus/subscribeToUserStatusUpdates (NIP-38) + ConnectionState plumbing & stall-watchdog wiring for half-open WS detection (Warp orange-icon case) + terminal session latch (auth rejection no longer racing back to reconnecting) — emitter + watchdog + reconnect policy logic extracted to relayConnectionStateEmitter.ts / relayStallWatchdog.ts / relayReconnectPolicy.ts
["src-tauri/src/migration.rs", 1010], // worktree shared-agent-data symlink sync (SHARED_AGENT_FILES + SHARED_AGENT_DIRS symlink-to-canonical + sibling pack migration) + mcp_command provider reconciliation + persona_pack_path reconciliation + tests
["src-tauri/src/commands/media.rs", 730], // ffmpeg video transcode + poster frame extraction + run_ffmpeg_with_timeout (find_ffmpeg via resolve_command, is_video_file, transcode_to_mp4, extract_poster_frame, transcode_and_extract_poster) + spawn_blocking wrappers + tests
["src-tauri/src/commands/agents.rs", 900], // remote agent lifecycle routing (local + provider branches) + scope enforcement + persona pack metadata wiring + mcp_toolsets field + NIP-OA auth_tag in deploy payload + relay-mesh client preflight (start_local_agent_with_preflight + StartTarget split: async ensure outside store lock, then sync spawn under lock)
["src-tauri/src/commands/agents.rs", 910], // remote agent lifecycle routing (local + provider branches) + scope enforcement + persona pack metadata wiring + mcp_toolsets field + NIP-OA auth_tag in deploy payload + relay-mesh client preflight (start_local_agent_with_preflight + StartTarget split: async ensure outside store lock, then sync spawn under lock)
["src-tauri/src/commands/messages.rs", 515], // feed multi-query + NIP-50 search + forum thread resolution + thread ref + reactions via REQ + edit_message media_tags param (Slack-style attachment-editable edits)
["src-tauri/src/nostr_convert.rs", 1150], // 12 Nostr event→model converters (channels, profiles, members, notes, search, agents, relay members) + rank_user_search_results helper for NIP-50 user search + 33 unit tests
["src-tauri/src/managed_agents/runtime.rs", 1385], // ... + respond-to gate env (SPROUT_ACP_RESPOND_TO[_ALLOWLIST]) + per-mode env builder + tests + persona/agent env_vars spawn merge (helper + tests now in env_vars.rs) + system-wide orphan sweep (proc_listallpids/proc on macOS, /proc on Linux) + SPROUT_MANAGED_AGENT env marker check (KERN_PROCARGS2 on macOS, /proc/environ on Linux), instance-scoped by app identifier so coexisting Sprout instances don't reap each other's agents + relay_mesh_model_id detector (mesh preset-env match) + tests
["src-tauri/src/managed_agents/runtime.rs", 1390], // ... + respond-to gate env (SPROUT_ACP_RESPOND_TO[_ALLOWLIST]) + per-mode env builder + tests + persona/agent env_vars spawn merge (helper + tests now in env_vars.rs) + system-wide orphan sweep (proc_listallpids/proc on macOS, /proc on Linux) + SPROUT_MANAGED_AGENT env marker check (KERN_PROCARGS2 on macOS, /proc/environ on Linux), instance-scoped by app identifier so coexisting Sprout instances don't reap each other's agents + relay_mesh_model_id detector (mesh preset-env match) + tests
["src-tauri/src/managed_agents/discovery.rs", 680], // KNOWN_ACP_PROVIDERS catalog + resolve_command cache + login_shell_path + classify_provider (four-state: Available/AdapterMissing/CliMissing/NotInstalled) + discover_acp_providers with dynamic install_hint + known_acp_provider/known_acp_provider_exact + normalize_agent_args + 15 unit tests
["src-tauri/src/managed_agents/types.rs", 745], // ManagedAgentRecord/Summary + Create/Update request structs + AcpProviderCatalogEntry + InstallRuntimeResult + RespondTo enum + validate_respond_to_allowlist + tests + persona/agent env_vars field
["src-tauri/src/managed_agents/backend.rs", 700], // provider IPC, validation, discovery, binary resolution + tests + redact_secrets_with for user env values + env_secrets_from_request + redact_env_values_in (shared with model discovery)
@@ -81,7 +81,7 @@ const overrides = new Map([
["src-tauri/src/huddle/tts.rs", 1380], // TTS pipeline + session warmup + cancel/shutdown handling + apply_fade_out (fade-out only — leading fade removed 2026-05-18 after onset-attenuation regression measured in examples/pocket_onset_probe.rs) + FIRST_APPEND_LEAD_IN_SAMPLES + build_sentence_append_plan (pure helper enforcing the lead-in fires exactly once per utterance, not per sentence — see lead_in_pad_fires_exactly_once_per_utterance regression test) + normalize_for_playback (per-sentence peak normalization to -3 dBFS ceiling with MAX_GAIN cap) + 30 unit tests (18 interrupt + 5 fade-out + 1 first-append-lead-in + 3 build-sentence-append-plan + 6 normalize)
["src-tauri/src/relay.rs", 510], // +4 lines for NIP-OA auth tag injection in profile sync (build_profile_event) + verification test
["src-tauri/src/commands/pairing.rs", 600], // NIP-AB pairing actor: 3 Tauri commands + background WS task + NIP-42 auth + NIP-43 probe + event parsing helpers
["src-tauri/src/lib.rs", 780], // + mesh command registrations + PairingHandle managed state + 3 pairing command registrations + parse_message_deep_link helper extracted with 6 unit tests covering empty-param filter regression + mod migration + sync_shared_agent_data/reconcile_provider_mcp_commands/reconcile_persona_pack_paths calls on launch + SIGINT/SIGTERM/SIGHUP signal handlers for agent process cleanup
["src-tauri/src/lib.rs", 835], // + mesh_llm_stubs module (feature-gated no-op Tauri commands) + mesh command registrations + PairingHandle managed state + 3 pairing command registrations + parse_message_deep_link helper extracted with 6 unit tests covering empty-param filter regression + mod migration + sync_shared_agent_data/reconcile_provider_mcp_commands/reconcile_persona_pack_paths calls on launch + SIGINT/SIGTERM/SIGHUP signal handlers for agent process cleanup
["src/shared/api/tauri.ts", 1212], // pairing command wrappers + applyWorkspace + NIP-44 encrypt/decrypt wrappers + observer_url field + relay member API functions (list/get/add/remove/change-role) + prevent sleep + AcpProviderCatalogEntry raw types + fromRawAcpProviderCatalogEntry converter + installAcpRuntime
]);
-6
View File
@@ -1,6 +0,0 @@
[env]
# Workaround for mesh-llm prepare-llama.sh `git -C` directory-escape bug.
# The script's git commands walk upward into the enclosing Sprout repo when
# .deps/llama.cpp/.git is missing. Disabling auto-build prevents the script
# from running. Remove once mesh-llm-sdk is bumped past Mesh-LLM/mesh-llm#780.
SKIPPY_LLAMA_AUTO_BUILD = "0"
+5 -1
View File
@@ -16,6 +16,10 @@ edition = "2021"
name = "sprout_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[features]
default = []
mesh-llm = ["dep:mesh-llm-sdk"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
@@ -57,7 +61,7 @@ url = "2"
sprout-core = { path = "../../crates/sprout-core" }
sprout-persona = { path = "../../crates/sprout-persona" }
sprout-sdk = { path = "../../crates/sprout-sdk" }
mesh-llm-sdk = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "bd16da4955a9dceee8f1e0a02c5814944f4e12a5", package = "mesh-llm-sdk", default-features = false, features = ["client", "serve"] }
mesh-llm-sdk = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "bd16da4955a9dceee8f1e0a02c5814944f4e12a5", package = "mesh-llm-sdk", default-features = false, features = ["client", "serve"], optional = true }
base64 = "0.22"
sha2 = "0.11"
tar = "0.4"
+3
View File
@@ -6,6 +6,7 @@ use std::{
use nostr::{Keys, ToBech32};
use tauri::{AppHandle, Manager};
#[cfg(feature = "mesh-llm")]
use tokio::sync::Mutex as AsyncMutex;
use crate::huddle::HuddleState;
@@ -35,6 +36,7 @@ pub struct AppState {
/// IOKit power assertion state — prevents idle sleep while agents run.
pub prevent_sleep: Arc<Mutex<crate::prevent_sleep::PreventSleepState>>,
/// In-process mesh-llm node started by Sprout Desktop.
#[cfg(feature = "mesh-llm")]
pub mesh_llm_runtime: AsyncMutex<Option<crate::mesh_llm::DesktopMeshRuntime>>,
}
@@ -81,6 +83,7 @@ pub fn build_app_state() -> AppState {
prevent_sleep: Arc::new(Mutex::new(
crate::prevent_sleep::PreventSleepState::default(),
)),
#[cfg(feature = "mesh-llm")]
mesh_llm_runtime: AsyncMutex::new(None),
}
}
+18 -7
View File
@@ -1,19 +1,21 @@
use nostr::{Keys, ToBech32};
use tauri::{AppHandle, State};
#[cfg(feature = "mesh-llm")]
use crate::managed_agents::relay_mesh_model_id;
use crate::{
app_state::AppState,
managed_agents::{
build_managed_agent_summary, discover_provider_candidates, ensure_persona_is_active,
find_managed_agent_mut, invoke_provider, load_managed_agents, load_personas,
managed_agent_avatar_url, managed_agent_log_path, managed_agents_base_dir,
normalize_agent_args, provider_deploy, read_log_tail, relay_mesh_model_id,
resolve_provider_binary, save_managed_agents, start_managed_agent_process,
stop_managed_agent_process, sync_managed_agent_processes, try_regenerate_nest,
validate_provider_config, BackendKind, BackendProviderInfo, CreateManagedAgentRequest,
CreateManagedAgentResponse, ManagedAgentLogResponse, ManagedAgentRecord,
ManagedAgentSummary, DEFAULT_ACP_COMMAND, DEFAULT_AGENT_COMMAND, DEFAULT_AGENT_PARALLELISM,
DEFAULT_AGENT_TURN_TIMEOUT_SECONDS, DEFAULT_MCP_COMMAND,
normalize_agent_args, provider_deploy, read_log_tail, resolve_provider_binary,
save_managed_agents, start_managed_agent_process, stop_managed_agent_process,
sync_managed_agent_processes, try_regenerate_nest, validate_provider_config, BackendKind,
BackendProviderInfo, CreateManagedAgentRequest, CreateManagedAgentResponse,
ManagedAgentLogResponse, ManagedAgentRecord, ManagedAgentSummary, DEFAULT_ACP_COMMAND,
DEFAULT_AGENT_COMMAND, DEFAULT_AGENT_PARALLELISM, DEFAULT_AGENT_TURN_TIMEOUT_SECONDS,
DEFAULT_MCP_COMMAND,
},
relay::{relay_ws_url_with_override, sync_managed_agent_profile},
util::now_iso,
@@ -27,6 +29,7 @@ fn workspace_owner_hex(state: &AppState) -> Result<String, String> {
Ok(keys.public_key().to_hex())
}
#[cfg(feature = "mesh-llm")]
async fn ensure_relay_mesh_for_record(
state: &AppState,
record: &ManagedAgentRecord,
@@ -38,6 +41,14 @@ async fn ensure_relay_mesh_for_record(
Ok(())
}
#[cfg(not(feature = "mesh-llm"))]
async fn ensure_relay_mesh_for_record(
_state: &AppState,
_record: &ManagedAgentRecord,
) -> Result<(), String> {
Ok(())
}
async fn start_local_agent_with_preflight(
app: &AppHandle,
state: &AppState,
+2
View File
@@ -12,6 +12,7 @@ mod identity_archive;
mod media;
mod media_download;
mod media_transcode;
#[cfg(feature = "mesh-llm")]
mod mesh_llm;
mod messages;
pub mod pairing;
@@ -36,6 +37,7 @@ pub use identity::*;
pub use identity_archive::*;
pub use media::*;
pub use media_download::*;
#[cfg(feature = "mesh-llm")]
pub use mesh_llm::*;
pub use messages::*;
pub use pairing::*;
+57
View File
@@ -4,6 +4,7 @@ mod events;
mod huddle;
mod managed_agents;
mod media_proxy;
#[cfg(feature = "mesh-llm")]
mod mesh_llm;
mod migration;
mod models;
@@ -13,6 +14,62 @@ mod relay;
mod templates;
mod util;
#[cfg(not(feature = "mesh-llm"))]
mod mesh_llm_stubs {
use tauri::State;
use crate::app_state::AppState;
type CmdResult<T> = Result<T, String>;
#[tauri::command]
pub async fn mesh_availability(_state: State<'_, AppState>) -> CmdResult<serde_json::Value> {
Err("mesh-llm feature not enabled".to_string())
}
#[tauri::command]
pub async fn mesh_start_node(
_app: tauri::AppHandle,
_state: State<'_, AppState>,
_request: serde_json::Value,
) -> CmdResult<serde_json::Value> {
Err("mesh-llm feature not enabled".to_string())
}
#[tauri::command]
pub async fn mesh_ensure_client_node(
_state: State<'_, AppState>,
_request: serde_json::Value,
) -> CmdResult<serde_json::Value> {
Err("mesh-llm feature not enabled".to_string())
}
#[tauri::command]
pub async fn mesh_stop_node(_state: State<'_, AppState>) -> CmdResult<serde_json::Value> {
Err("mesh-llm feature not enabled".to_string())
}
#[tauri::command]
pub async fn mesh_node_status(_state: State<'_, AppState>) -> CmdResult<serde_json::Value> {
Err("mesh-llm feature not enabled".to_string())
}
#[tauri::command]
pub async fn mesh_installed_models(
_state: State<'_, AppState>,
) -> CmdResult<Vec<serde_json::Value>> {
Err("mesh-llm feature not enabled".to_string())
}
#[tauri::command]
pub fn mesh_agent_preset(_request: serde_json::Value) -> CmdResult<serde_json::Value> {
Err("mesh-llm feature not enabled".to_string())
}
}
#[cfg(not(feature = "mesh-llm"))]
use mesh_llm_stubs::*;
use app_state::{build_app_state, resolve_persisted_identity, AppState};
use commands::*;
use huddle::audio_output::{
+23 -16
View File
@@ -1,7 +1,8 @@
#[cfg(feature = "mesh-llm")]
use super::relay_mesh_model_id;
use super::{
find_managed_agent_mut, kill_stale_tracked_processes, load_managed_agents, relay_mesh_model_id,
save_managed_agents, spawn_agent_child, sync_managed_agent_processes, BackendKind,
ManagedAgentProcess,
find_managed_agent_mut, kill_stale_tracked_processes, load_managed_agents, save_managed_agents,
spawn_agent_child, sync_managed_agent_processes, BackendKind, ManagedAgentProcess,
};
use crate::app_state::AppState;
use crate::util;
@@ -103,20 +104,25 @@ pub async fn restore_managed_agents_on_launch(
.ok()
.map(|k| k.public_key().to_hex());
let mut mesh_preflight_failures = std::collections::HashSet::new();
for record in &agents_to_start {
let Some(model_id) = relay_mesh_model_id(record) else {
continue;
};
if let Err(error) = crate::commands::ensure_client_node_for_model(&state, model_id).await {
persist_restore_error(app, &state, &record.pubkey, error)?;
mesh_preflight_failures.insert(record.pubkey.clone());
#[cfg(feature = "mesh-llm")]
let agents_to_start = {
let mut mesh_preflight_failures = std::collections::HashSet::new();
for record in &agents_to_start {
let Some(model_id) = relay_mesh_model_id(record) else {
continue;
};
if let Err(error) =
crate::commands::mesh_llm::ensure_client_node_for_model(&state, model_id).await
{
persist_restore_error(app, &state, &record.pubkey, error)?;
mesh_preflight_failures.insert(record.pubkey.clone());
}
}
}
let agents_to_start: Vec<_> = agents_to_start
.into_iter()
.filter(|record| !mesh_preflight_failures.contains(&record.pubkey))
.collect();
agents_to_start
.into_iter()
.filter(|record| !mesh_preflight_failures.contains(&record.pubkey))
.collect::<Vec<_>>()
};
if agents_to_start.is_empty() {
return Ok(());
}
@@ -183,6 +189,7 @@ pub async fn restore_managed_agents_on_launch(
Ok(())
}
#[cfg(feature = "mesh-llm")]
fn persist_restore_error(
app: &tauri::AppHandle,
state: &AppState,
@@ -1046,6 +1046,7 @@ fn child_rust_log_filter() -> String {
/// Returns the relay-mesh model id for agents whose provider env points at the
/// local mesh client endpoint. The caller is responsible for ensuring that
/// endpoint is alive before spawning the agent process.
#[cfg(feature = "mesh-llm")]
pub fn relay_mesh_model_id(record: &ManagedAgentRecord) -> Option<String> {
let base_url = record.env_vars.get("OPENAI_COMPAT_BASE_URL")?.trim();
if base_url.trim_end_matches('/') != "http://127.0.0.1:9337/v1" {
@@ -1169,6 +1170,7 @@ pub fn stop_managed_agent_process(
#[cfg(test)]
mod tests {
#[cfg(feature = "mesh-llm")]
use super::relay_mesh_model_id;
use crate::managed_agents::known_acp_provider;
@@ -1261,6 +1263,7 @@ mod tests {
}
}
#[cfg(feature = "mesh-llm")]
#[test]
fn relay_mesh_model_id_detects_mesh_preset_env() {
let mut rec = fixture(RespondTo::OwnerOnly, vec![], Some("tag".into()));
@@ -1276,6 +1279,7 @@ mod tests {
assert_eq!(relay_mesh_model_id(&rec).as_deref(), Some("Qwen3"));
}
#[cfg(feature = "mesh-llm")]
#[test]
fn relay_mesh_model_id_ignores_non_mesh_openai_env() {
let mut rec = fixture(RespondTo::OwnerOnly, vec![], Some("tag".into()));
+3 -3
View File
@@ -181,7 +181,7 @@ desktop-release-build target="aarch64-apple-darwin":
touch "desktop/src-tauri/binaries/git-credential-nostr-$TARGET"
touch "desktop/src-tauri/binaries/sprout-$TARGET"
pnpm install
cd {{desktop_dir}} && pnpm tauri build --target {{target}}
cd {{desktop_dir}} && TAURI_CLI_EXTRA_CARGO_ARGS="--features=mesh-llm" pnpm tauri build --target {{target}}
# Run desktop checks suitable for CI / pre-push
desktop-ci: desktop-check desktop-test desktop-tauri-fmt-check desktop-build desktop-tauri-check desktop-tauri-test
@@ -263,7 +263,7 @@ dev *ARGS: _ensure-sidecar-stubs
[[ -d node_modules ]] || pnpm install
source ../scripts/instance-env.sh
echo "Starting on Vite port ${SPROUT_VITE_PORT}, relay ${SPROUT_RELAY_URL}"
pnpm exec tauri dev --config "$SPROUT_TAURI_CONFIG" {{ARGS}}
TAURI_CLI_EXTRA_CARGO_ARGS="--features=mesh-llm" pnpm exec tauri dev --config "$SPROUT_TAURI_CONFIG" {{ARGS}}
# Run the desktop app against the internal staging relay (installs deps + builds agent tools automatically)
staging *ARGS: _ensure-sidecar-stubs
@@ -279,7 +279,7 @@ staging *ARGS: _ensure-sidecar-stubs
source ../scripts/instance-env.sh
export SPROUT_RELAY_URL="wss://sprout-oss.stage.blox.sqprod.co"
echo "Starting staging on Vite port ${SPROUT_VITE_PORT}, relay ${SPROUT_RELAY_URL}"
pnpm exec tauri dev --config "$SPROUT_TAURI_CONFIG" {{ARGS}}
TAURI_CLI_EXTRA_CARGO_ARGS="--features=mesh-llm" pnpm exec tauri dev --config "$SPROUT_TAURI_CONFIG" {{ARGS}}
# Run the desktop frontend dev server (port derived from worktree)
desktop-dev: