Skip to content
Merged
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
6 changes: 3 additions & 3 deletions desktop/scripts/check-file-sizes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
]);

Expand Down
6 changes: 0 additions & 6 deletions desktop/src-tauri/.cargo/config.toml

This file was deleted.

6 changes: 5 additions & 1 deletion desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [] }

Expand Down Expand Up @@ -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"
Expand Down
3 changes: 3 additions & 0 deletions desktop/src-tauri/src/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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>>,
}

Expand Down Expand Up @@ -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),
}
}
Expand Down
25 changes: 18 additions & 7 deletions desktop/src-tauri/src/commands/agents.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions desktop/src-tauri/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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::*;
Expand Down
57 changes: 57 additions & 0 deletions desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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::{
Expand Down
39 changes: 23 additions & 16 deletions desktop/src-tauri/src/managed_agents/restore.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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(());
}
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions desktop/src-tauri/src/managed_agents/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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()));
Expand All @@ -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()));
Expand Down
6 changes: 3 additions & 3 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down