mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
mesh-llm: B6 kind:31990 publisher + settings-card publish-on-save
desktop/src-tauri/src/commands/mesh_llm.rs: new mesh_publish_offer command. Reads persisted prefs and the local iroh endpoint id; on enabled=true, builds a kind:31990 event with the JSON-serialised MeshLlmOffer envelope and a 'd' tag matching prefs.d_tag, then signs + POSTs via the existing submit_event pipeline (NIP-98 to /events). On enabled=false, publishes the *same address* with empty content — NIP-33's 'delete by replace' idiom — so consumers know the offer has been withdrawn. PublishOfferResult.published_offer reports which path was taken. desktop/src/features/settings/ui/MeshComputeSettingsCard.tsx: persist() now follows save-prefs with a relay capability probe and (when the relay advertises iroh_relay_url) a publish call. If the relay doesn't support mesh-LLM, prefs are still saved locally and the UI surfaces a specific 'this relay does not advertise iroh_relay_url' message rather than a confusing 'publish failed'. The user-facing flow is now: open settings -> Share compute -> toggle on -> a kind:31990 event hits the relay, NIP-43-fanned-out to other members. Toggling off publishes the empty-content replacement. Tests: 208 sprout-relay, 174 sprout-core, 11 desktop mesh_llm — all unchanged-and-pass. desktop typecheck + biome clean. Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com> Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
@@ -3,16 +3,20 @@
|
||||
//! All commands deal with *the local user's own* mesh-LLM state:
|
||||
//! - the persisted iroh endpoint id,
|
||||
//! - the persisted compute-sharing preferences (the avatar-menu sliders),
|
||||
//! - explicit toggle/save calls invoked when the user changes the prefs.
|
||||
//! - explicit toggle/save calls invoked when the user changes the prefs,
|
||||
//! - publishing / deleting the user's kind:31990 compute-offer event.
|
||||
//!
|
||||
//! Discovering and connecting to *other* members' offers happens through the
|
||||
//! existing relay WebSocket pipeline, not these commands.
|
||||
//! Discovering *other* members' offers happens through the relay
|
||||
//! WebSocket pipeline already exposed by `relayClientSession.ts`.
|
||||
|
||||
use nostr::{EventBuilder, Kind, Tag};
|
||||
use serde::Serialize;
|
||||
use sprout_core::kind::KIND_MESH_LLM_DISCOVERY;
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
use crate::app_state::AppState;
|
||||
use crate::mesh_llm;
|
||||
use crate::relay::submit_event;
|
||||
|
||||
/// Result type for mesh-LLM commands: errors are surfaced as user-facing
|
||||
/// strings by the frontend.
|
||||
@@ -70,3 +74,74 @@ pub async fn mesh_relay_iroh_url(
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
// ── Publisher ──────────────────────────────────────────────────────────────
|
||||
|
||||
/// Result of `mesh_publish_offer` — surface enough state so the frontend
|
||||
/// can show the user *which* offer just went on the wire.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct PublishOfferResult {
|
||||
/// `event_id` returned by the relay on accept.
|
||||
pub event_id: String,
|
||||
/// `true` if compute-sharing is currently enabled. When false, the
|
||||
/// command publishes an *empty-content* kind:31990 event at the same
|
||||
/// `(pubkey, d_tag)` address, which under NIP-33 is the canonical way
|
||||
/// to indicate "this offer is no longer active". Consumers that observe
|
||||
/// the empty content drop the offer from their cache.
|
||||
pub published_offer: bool,
|
||||
}
|
||||
|
||||
/// Publish (or revoke) the user's kind:31990 compute-offer event.
|
||||
///
|
||||
/// Reads the current prefs from disk and the local iroh endpoint id. If
|
||||
/// `enabled = true`, builds a kind:31990 with the offer envelope content
|
||||
/// and the matching `d` tag; signs and POSTs via the existing
|
||||
/// [`submit_event`] pipeline (NIP-98-authenticated to the configured relay).
|
||||
/// If `enabled = false`, publishes the *same address* with empty content
|
||||
/// to tell consumers the offer has been retired.
|
||||
///
|
||||
/// `iroh_relay_url` should be the relay's NIP-11 `iroh_relay_url` (fetched
|
||||
/// via [`mesh_relay_iroh_url`] at session start). The offer envelope
|
||||
/// carries it so consumers know where to dial.
|
||||
#[tauri::command]
|
||||
pub async fn mesh_publish_offer(
|
||||
app: AppHandle,
|
||||
state: State<'_, AppState>,
|
||||
iroh_relay_url: String,
|
||||
) -> CmdResult<PublishOfferResult> {
|
||||
// Load prefs + endpoint key. These are sync; complete before any await.
|
||||
let prefs = mesh_llm::offer::load_prefs(&app).map_err(|e| e.to_string())?;
|
||||
let endpoint_key =
|
||||
mesh_llm::load_or_create_endpoint_key(&app).map_err(|e| e.to_string())?;
|
||||
let endpoint_id_str = endpoint_key.public().to_string();
|
||||
|
||||
let d_tag = prefs.d_tag.clone();
|
||||
let d_tag_tag = Tag::parse(["d", &d_tag]).map_err(|e| format!("d tag: {e}"))?;
|
||||
|
||||
let (content, published_offer) = if prefs.enabled {
|
||||
let offer = prefs
|
||||
.build_offer(&endpoint_id_str, &iroh_relay_url)
|
||||
.ok_or_else(|| {
|
||||
"build_offer returned None despite enabled=true (logic bug)".to_string()
|
||||
})?;
|
||||
if !offer.is_publishable() {
|
||||
return Err("offer envelope failed publishable check".to_string());
|
||||
}
|
||||
let json = serde_json::to_string(&offer).map_err(|e| format!("serialise: {e}"))?;
|
||||
(json, true)
|
||||
} else {
|
||||
// NIP-33 "delete by replace": same (pubkey, kind, d) address, empty
|
||||
// content. Consumers must treat an empty content as 'offer
|
||||
// withdrawn'.
|
||||
(String::new(), false)
|
||||
};
|
||||
|
||||
let builder = EventBuilder::new(Kind::Custom(KIND_MESH_LLM_DISCOVERY as u16), content)
|
||||
.tags(vec![d_tag_tag]);
|
||||
|
||||
let res = submit_event(builder, &state).await?;
|
||||
Ok(PublishOfferResult {
|
||||
event_id: res.event_id,
|
||||
published_offer,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -459,6 +459,7 @@ pub fn run() {
|
||||
mesh_get_sharing_prefs,
|
||||
mesh_set_sharing_prefs,
|
||||
mesh_relay_iroh_url,
|
||||
mesh_publish_offer,
|
||||
discover_acp_providers,
|
||||
discover_managed_agent_prereqs,
|
||||
sign_event,
|
||||
|
||||
@@ -85,8 +85,25 @@ export function MeshComputeSettingsCard() {
|
||||
setSaving(true);
|
||||
setError(null);
|
||||
try {
|
||||
// Save first so a failed publish leaves the prefs in a sane state.
|
||||
await invoke("mesh_set_sharing_prefs", { prefs: next });
|
||||
setPrefs(next);
|
||||
|
||||
// Probe the connected relay for its iroh_relay_url. If it doesn't
|
||||
// advertise mesh-LLM at all, the offer can't be published — but the
|
||||
// local prefs are still saved (the user might re-connect to a
|
||||
// mesh-capable relay later).
|
||||
const relayWsUrl = await invoke<string>("get_relay_ws_url");
|
||||
const irohUrl = await invoke<string | null>("mesh_relay_iroh_url", {
|
||||
relayWsUrl,
|
||||
});
|
||||
if (irohUrl) {
|
||||
await invoke("mesh_publish_offer", { irohRelayUrl: irohUrl });
|
||||
} else if (next.enabled) {
|
||||
setError(
|
||||
"Saved locally, but this relay does not advertise iroh_relay_url — your offer will not be visible to other members until the relay is configured for mesh-LLM.",
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
setError(String(e));
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user