From 3cd84e68fe220a2990de1cb6c0edd6639fd44bb1 Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Tue, 2 Jun 2026 15:56:45 -0400 Subject: [PATCH 1/2] refactor(desktop): feature-gate mesh-llm-sdk behind optional Cargo feature The skippy-ffi build.rs panics in fresh worktrees and CI when pre-built llama.cpp archives are missing and SKIPPY_LLAMA_AUTO_BUILD=0. Rather than requiring archives to exist, make mesh-llm-sdk an optional dep (default OFF) so skippy-ffi never compiles unless explicitly opted in. Stub Tauri commands return errors when the feature is off so the frontend IPC contract is preserved. The dev, staging, and release-build Justfile recipes pass --features=mesh-llm via TAURI_CLI_EXTRA_CARGO_ARGS. Removes the broken SKIPPY_LLAMA_AUTO_BUILD=0 workaround from #819. --- desktop/scripts/check-file-sizes.mjs | 6 +- desktop/src-tauri/.cargo/config.toml | 6 -- desktop/src-tauri/Cargo.toml | 6 +- desktop/src-tauri/src/app_state.rs | 3 + desktop/src-tauri/src/commands/agents.rs | 25 +++++--- desktop/src-tauri/src/commands/mod.rs | 2 + desktop/src-tauri/src/lib.rs | 57 +++++++++++++++++++ .../src-tauri/src/managed_agents/restore.rs | 15 +++-- .../src-tauri/src/managed_agents/runtime.rs | 4 ++ justfile | 6 +- 10 files changed, 106 insertions(+), 24 deletions(-) delete mode 100644 desktop/src-tauri/.cargo/config.toml diff --git a/desktop/scripts/check-file-sizes.mjs b/desktop/scripts/check-file-sizes.mjs index 73d865d55e9..067d753cfe3 100644 --- a/desktop/scripts/check-file-sizes.mjs +++ b/desktop/scripts/check-file-sizes.mjs @@ -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 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 ]); diff --git a/desktop/src-tauri/.cargo/config.toml b/desktop/src-tauri/.cargo/config.toml deleted file mode 100644 index dd1e15abc88..00000000000 --- a/desktop/src-tauri/.cargo/config.toml +++ /dev/null @@ -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" diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml index 5094e98a5c0..3afcac6d1da 100644 --- a/desktop/src-tauri/Cargo.toml +++ b/desktop/src-tauri/Cargo.toml @@ -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 = [] } @@ -56,7 +60,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" diff --git a/desktop/src-tauri/src/app_state.rs b/desktop/src-tauri/src/app_state.rs index ed85521f0ad..d901fa86e87 100644 --- a/desktop/src-tauri/src/app_state.rs +++ b/desktop/src-tauri/src/app_state.rs @@ -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>, /// In-process mesh-llm node started by Sprout Desktop. + #[cfg(feature = "mesh-llm")] pub mesh_llm_runtime: AsyncMutex>, } @@ -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), } } diff --git a/desktop/src-tauri/src/commands/agents.rs b/desktop/src-tauri/src/commands/agents.rs index 4302d4203dc..18a1fc95e09 100644 --- a/desktop/src-tauri/src/commands/agents.rs +++ b/desktop/src-tauri/src/commands/agents.rs @@ -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 { 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, diff --git a/desktop/src-tauri/src/commands/mod.rs b/desktop/src-tauri/src/commands/mod.rs index 23898021cc4..e26c7a4924f 100644 --- a/desktop/src-tauri/src/commands/mod.rs +++ b/desktop/src-tauri/src/commands/mod.rs @@ -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::*; diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index 8cd568fb7b8..4738269399d 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -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 = Result; + + #[tauri::command] + pub async fn mesh_availability(_state: State<'_, AppState>) -> CmdResult { + 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 { + 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 { + Err("mesh-llm feature not enabled".to_string()) + } + + #[tauri::command] + pub async fn mesh_stop_node(_state: State<'_, AppState>) -> CmdResult { + Err("mesh-llm feature not enabled".to_string()) + } + + #[tauri::command] + pub async fn mesh_node_status(_state: State<'_, AppState>) -> CmdResult { + Err("mesh-llm feature not enabled".to_string()) + } + + #[tauri::command] + pub async fn mesh_installed_models( + _state: State<'_, AppState>, + ) -> CmdResult> { + Err("mesh-llm feature not enabled".to_string()) + } + + #[tauri::command] + pub fn mesh_agent_preset(_request: serde_json::Value) -> CmdResult { + 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::{ diff --git a/desktop/src-tauri/src/managed_agents/restore.rs b/desktop/src-tauri/src/managed_agents/restore.rs index b6819aba2e9..ffbf6fb8301 100644 --- a/desktop/src-tauri/src/managed_agents/restore.rs +++ b/desktop/src-tauri/src/managed_agents/restore.rs @@ -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,16 +104,21 @@ pub async fn restore_managed_agents_on_launch( .ok() .map(|k| k.public_key().to_hex()); + #[cfg(feature = "mesh-llm")] let mut mesh_preflight_failures = std::collections::HashSet::new(); + #[cfg(feature = "mesh-llm")] 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 { + 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()); } } + #[cfg(feature = "mesh-llm")] let agents_to_start: Vec<_> = agents_to_start .into_iter() .filter(|record| !mesh_preflight_failures.contains(&record.pubkey)) @@ -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, diff --git a/desktop/src-tauri/src/managed_agents/runtime.rs b/desktop/src-tauri/src/managed_agents/runtime.rs index 2b888a18f3d..576ce57c3aa 100644 --- a/desktop/src-tauri/src/managed_agents/runtime.rs +++ b/desktop/src-tauri/src/managed_agents/runtime.rs @@ -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 { 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())); diff --git a/justfile b/justfile index 09738c9b628..5caf8f552db 100644 --- a/justfile +++ b/justfile @@ -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: From a208bc166d802aa14951d36b776cdd0e9da1abf1 Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Tue, 2 Jun 2026 16:13:46 -0400 Subject: [PATCH 2/2] refactor(desktop): consolidate mesh preflight cfg into single block Wrap the mesh_preflight_failures set, preflight loop, and filtered agents_to_start rebind in a single #[cfg(feature = "mesh-llm")] block expression instead of three separate annotations on consecutive statements. --- desktop/scripts/check-file-sizes.mjs | 2 +- .../src-tauri/src/managed_agents/restore.rs | 34 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/desktop/scripts/check-file-sizes.mjs b/desktop/scripts/check-file-sizes.mjs index 067d753cfe3..4cef8e671e8 100644 --- a/desktop/scripts/check-file-sizes.mjs +++ b/desktop/scripts/check-file-sizes.mjs @@ -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", 835], // + 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 ]); diff --git a/desktop/src-tauri/src/managed_agents/restore.rs b/desktop/src-tauri/src/managed_agents/restore.rs index ffbf6fb8301..4c09aaf4264 100644 --- a/desktop/src-tauri/src/managed_agents/restore.rs +++ b/desktop/src-tauri/src/managed_agents/restore.rs @@ -105,24 +105,24 @@ pub async fn restore_managed_agents_on_launch( .map(|k| k.public_key().to_hex()); #[cfg(feature = "mesh-llm")] - let mut mesh_preflight_failures = std::collections::HashSet::new(); - #[cfg(feature = "mesh-llm")] - 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 = { + 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()); + } } - } - #[cfg(feature = "mesh-llm")] - 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::>() + }; if agents_to_start.is_empty() { return Ok(()); }