Commit Graph

163 Commits

Author SHA1 Message Date
Michael Neale
2d53bc2ecc Pin mesh SDK example to direct shutdown commit 2026-05-29 14:22:44 +10:00
Michael Neale
5b9f018d84 showing how to use nascent sdk approach 2026-05-29 13:49:15 +10:00
tlongwell-block
1cc536b03e mesh-llm: silence dead_code in client-side NIP-98 bearer module
The verifier side of the iroh-relay NIP-98 admission is fully wired and
tested in sprout-relay::iroh_relay. The client side (build_nip98_bearer)
is reserved for the deferred dial path — it's complete and unit-tested
on its own, but has no live caller until upstream mesh-llm PR A lands.

Adds a module-level #![allow(dead_code)] with a comment pointing at the
deferred-dial dependency, so the workspace stays warning-clean without
losing the test coverage on the helper.

cargo clippy --workspace -- -D warnings + cargo fmt --check 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>
2026-05-21 08:57:28 -04:00
tlongwell-block
f711e37a7a mesh-llm: Max review fixups — expires_at, EndpointAddr, same-relay filter
Per Max's pre-PR review of d9a791f:

1. Add MeshLlmOffer.expires_at: u64 (unix seconds). Hard-required by
   serde (no default) since consumers depend on it for correctness:
   crashed publishers cannot send the NIP-33 delete-by-replace
   tombstone, so the TTL is the only thing that reaps stale offers.

   New helpers + tests in sprout-core::mesh_llm:
   - is_expired(now) returns true when expires_at <= now.
   - matches_local_relay(current_relay) compares against the relay's
     NIP-11 iroh_relay_url with lightweight canonicalisation
     (lower-case scheme/host, trailing-slash collapse, query/fragment
     drop). v1 invariant: one relay = one mesh boundary.
   - is_publishable now rejects expires_at == 0.
   - canonical_relay_url() is a small private helper, deliberately
     separate from sprout_auth::nip98_canonical_url (different jobs).

   5 new tests (expires_at_required, is_expired_filter,
   matches_local_relay_canonicalises, is_publishable_rejects_zero_*,
   plus updated JSON fixtures throughout).

2. Fix doc wording: iroh NodeAddr -> EndpointAddr (rc.0 naming).
   Soften 'multiple relays bridging into membership scope' language to
   reflect that v1 is strictly single-relay and the field is reserved
   for future cross-relay only.

3. Desktop publisher (mesh_publish_offer) now computes
   expires_at = now + OFFER_TTL_SECS (15 min) and threads it through
   build_offer(endpoint_id, iroh_relay_url, expires_at). The frontend
   hook is expected to re-invoke publish on heartbeat well before the
   deadline (heartbeat plumbing is in the next commit).

4. Frontend useMeshLlmOffers hook:
   - probes the relay's NIP-11 iroh_relay_url once on mount and
     filters offers whose iroh_relay_url doesn't match (v1 same-relay
     invariant). When the relay doesn't advertise mesh-LLM, the filter
     correctly empties the list.
   - rejects events with schema v != 1 before storing.
   - 30s setInterval ticks 'now' so expired offers drop without waiting
     for a new event. O(1) timer regardless of how many offers are in
     the cache.
   - mirrors the canonicaliser logic from sprout-core in TS so JS-side
     accept/reject decisions match the Rust check.

Tests after change:
- sprout-core --lib: 174 -> 178 (+4 mesh_llm)
- desktop mesh_llm --lib: 11 unchanged (publisher signature change
  required updating build_offer call sites; tests pass exact-same set).
- pnpm typecheck + pnpm check (biome + file-sizes): clean.
- cargo clippy --workspace -- -D warnings: clean.
- cargo fmt --all -- --check: 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>
2026-05-19 17:20:29 -04:00
tlongwell-block
c5ec624ebc 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>
2026-05-19 17:06:52 -04:00
tlongwell-block
d8ca1a9c3e mesh-llm: B7 Tauri commands surface
desktop/src-tauri/src/commands/mesh_llm.rs (new):
- mesh_get_endpoint_id(app) -> { endpoint_id }: creates the persisted
  iroh keypair on first call; returns the canonical Display form of
  the public key so the UI can show 'this device' identity.
- mesh_get_sharing_prefs(app) -> ComputeSharingPrefs: reads the
  persisted prefs file (defaults applied when absent).
- mesh_set_sharing_prefs(app, prefs) -> (): atomic write-through.
- mesh_relay_iroh_url(state, relay_ws_url) -> Option<String>: probes
  the relay's NIP-11 doc for iroh_relay_url; returns None gracefully
  when the relay doesn't advertise mesh-LLM.

All four registered in lib.rs's tauri::generate_handler! block.
Frontend hooks land in C1 (avatar-menu MeshComputeSettingsCard).

Cleanup: drop wildcard re-exports from mesh_llm/mod.rs so unused
publisher/dialer helpers don't trip top-level dead-code lints before
B5/B6 are wired in.

cargo check passes; clippy + fmt clean (relay-side workspace).

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>
2026-05-19 16:59:57 -04:00
tlongwell-block
000c78c203 mesh-llm: B1 NIP-11 fetcher for iroh_relay_url
desktop/src-tauri/src/mesh_llm/nip11.rs: fetch_iroh_relay_url(ws_url)
converts ws:// -> http://, GETs / with Accept: application/nostr+json,
extracts the iroh_relay_url field from the NIP-11 JSON. Returns
Ok(None) on unreachable relays / malformed responses / missing field
so mesh-LLM silently disables itself rather than producing a deploy
mystery; only returns Err for un-fixable caller mistakes (non-ws URL).

Mirrors the probe_relay_supports_nip43 helper already in
commands/pairing.rs; deliberately doesn't share code since this is a
different decode shape with different graceful-failure semantics.

desktop mesh_llm tests: 9 -> 11 (+2 nip11 helper).

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>
2026-05-19 16:58:14 -04:00
tlongwell-block
1dbc89cc11 mesh-llm: offer envelope (sprout-core) + desktop building blocks
B0 — sprout-core/src/mesh_llm.rs (new): MeshLlmOffer envelope, the
content of a kind:31990 event. Schema versioned (v: u32), with
deny_unknown_fields at the top level and a freeform 'extra' Value
escape hatch. ResourceCaps + ModelOffer sub-structs. d_tag charset is
limited to [A-Za-z0-9_-] (NIP-33 stability). 9 unit tests covering
round-trip JSON, optional caps, unknown-field rejection, d_tag
validation, is_publishable rule set.

B2 — desktop/src-tauri/src/mesh_llm/endpoint.rs: persists the iroh
endpoint keypair to {app_data_dir}/mesh_iroh.key as 32 hex bytes.
Atomic write via tempfile.persist; corrupt files quarantined to
.bad.{epoch} (same pattern as identity.key). 2 unit tests.

  Design note in the module doc: we deliberately do NOT derive the
  iroh key from the Nostr key, because that would couple key rotation
  (rotating the Nostr key would silently break active offers) and
  invent a new key-custody convention. Separate file, same Tauri
  sandbox.

B3 — desktop/src-tauri/src/mesh_llm/nip98.rs: build_nip98_bearer(keys,
iroh_relay_public_url) signs a kind:27235 event with the user's Nostr
key over the canonical relay URL (sprout_auth::nip98_canonical_url
with path '/relay'), base64-encodes the event JSON. This is the exact
token the relay's iroh_relay::verify_bearer decodes + verifies.
3 unit tests.

B-offer prefs — desktop/src-tauri/src/mesh_llm/offer.rs: persisted
ComputeSharingPrefs (the avatar-menu sliders). Default is disabled,
1 concurrent consumer cap. build_offer() returns None when disabled
so callers know to *delete* any prior offer rather than re-publish.
JSON round-trip + helper tests, 4 tests.

Workspace deps added:
- desktop pulls sprout-auth (for the canonical URL helper, nostr-free
  at the API surface so the 0.36/0.37 nostr split doesn't matter).
- desktop pulls iroh-base = =1.0.0-rc.0 with the 'key' feature for
  SecretKey/PublicKey/EndpointId.
- desktop pulls thiserror = '2'.

Tests: 9 desktop mesh_llm tests pass + 9 sprout-core mesh_llm tests
pass. Workspace clippy + fmt clean (relay side; desktop has expected
dead_code warnings until B4-B6 wire these in).

Note: desktop crate requires sidecar binary stubs in
desktop/src-tauri/binaries/ to typecheck; created via the existing
'just _ensure-sidecar-stubs' helper.

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>
2026-05-19 16:55:45 -04:00
Will Pfleger
50b350dc58 fix(desktop): worktree support — workspace boundary + shared identity (#610) 2026-05-18 19:11:34 -04:00
tlongwell-block
6e1418e571 feat(huddle): fix concurrent-speaker mixing + jitter buffer + protocol v2 (up to 10 peers) (#609)
Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
2026-05-18 16:34:52 -04:00
tlongwell-block
c0eb5affcf refactor(huddle): switch local TTS to Pocket TTS (Kyutai/sherpa-onnx) (#607)
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: npub1cc3ha7z055mu0rwwu7806t2wt8mj3pvu0uv5mfp2c50dahaqhczshdalg6 <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1cc3ha7z055mu0rwwu7806t2wt8mj3pvu0uv5mfp2c50dahaqhczshdalg6 <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
2026-05-18 14:28:27 -04:00
tlongwell-block
3daf8d6ae1 feat(huddle): swap STT default to Parakeet TDT-CTC 110M (EN, int8) (#562)
Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
2026-05-15 22:22:05 -04:00
tlongwell-block
d1097aa91f chore(desktop): upgrade Tauri to 2.11.x to fix blank window after sleep on macOS (#600)
Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
2026-05-15 18:59:16 -04:00
tlongwell-block
7171d4954d fix(relay,desktop): only advertise NIP-43 when enforced; probe pairing by supported_nips (#601)
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
2026-05-15 15:11:37 -04:00
tlongwell-block
32347d117c feat(desktop): per-persona and per-agent env var overrides (#594) 2026-05-15 10:43:00 -04:00
tlongwell-block
13dc0df688 fix(desktop): stop pinning agents to deprecated SPROUT_ACP_TURN_TIMEOUT (#592)
Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
2026-05-14 21:51:38 -04:00
tlongwell-block
5cdb3062ed fix(desktop): populate member_count in get_channels so channel browser shows real counts (#548)
Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
2026-05-14 20:38:01 -04:00
tlongwell-block
9a403d3e8a fix: channel member search via NIP-50 + Typesense indexer fix for kind:0 (#569)
Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
2026-05-13 15:03:35 -04:00
Wes
8716aefa3c fix(desktop): use resolve_command for ffmpeg discovery in sandboxed app (#568)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-13 11:00:00 -07:00
tlongwell-block
9c258e4beb feat(desktop): GUI for agent respond-to gate (owner-only / anyone / allowlist) (#557) 2026-05-13 12:48:42 -04:00
tlongwell-block
61c30add40 feat(desktop): multi-select in the paperclip file picker (#551)
Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
2026-05-12 15:01:48 -04:00
tlongwell-block
effa24f06c fix(desktop): forward cache headers in media proxy (#549)
Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
2026-05-12 14:40:07 -04:00
Wes
11778f4250 feat(desktop): image download, drag-drop prevention, and drop zone indicator (#542)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-11 23:26:12 +00:00
Wes
1b9f6169ee feat(desktop): channel templates — reusable project settings (#538)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-11 12:31:25 -07:00
tlongwell-block
e2b6e8b25a Use dedicated sprout-agent avatar (#532) 2026-05-11 17:16:08 +00:00
tlongwell-block
22f1e0b724 Launch sprout-agent from the desktop GUI (#529) 2026-05-11 12:36:53 -04:00
tlongwell-block
9e8302c35f fix: send x-auth-tag header in agent profile sync (#522) 2026-05-10 20:43:00 -04:00
tlongwell-block
90b0af136d fix: resolve media proxy relay URL dynamically per-request (#518) 2026-05-09 02:58:58 +00:00
Wes
0f10861850 feat(desktop): expand default agent toolsets with canvas, forums, dms, and member list (#510)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-08 10:10:06 -07:00
Wes
ff0806523a prompts(kit, scout): add @-mention discipline section (#506)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 06:15:42 +00:00
Wes
27bcd45cc9 Refresh built-in personas: Solo, Kit, Scout + Kit & Scout team (#499)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 18:22:26 +00:00
Wes
3d585dc044 fix(desktop): prevent-sleep spawn convention + expiry badge (#487)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-05 14:44:12 -07:00
tlongwell-block
d90bd768c5 fix(desktop): fetch open channels for channel browser discovery (#488) 2026-05-05 21:11:13 +00:00
Wes
ec489c5b1a feat(desktop): add keep awake while agents are active setting (#484)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-05 13:14:00 -07:00
tlongwell-block
ea62664759 post-475 fixes (#481)
Co-authored-by: Wes <wesb@block.xyz>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 14:41:18 -04:00
tlongwell-block
454781caae Sprout speaks Nostr. Nothing else. (#475) 2026-05-05 12:03:06 -04:00
Wes
4d0cd58d35 feat: pnpm workspace, deep links, and web repos page (#476)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-04 17:09:25 -07:00
Bradley Axen
c1063a0c92 Update onboarding flow to support membership checks and BYOK (#457) 2026-05-04 19:48:14 +00:00
tlongwell-block
8916825c45 feat(pairing): auto-detect NIP-43 relay and route target to /pair sidecar (#470) 2026-05-03 22:12:14 -04:00
Wes
e105c9ea5e feat(desktop): add relay membership admin UI in settings (#454)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-02 16:56:41 +00:00
tlongwell-block
c9f287958d feat(desktop): wire git-credential-nostr into agent spawn path (#451) 2026-05-02 11:46:49 -04:00
tlongwell-block
f23560d3d4 feat: Image & video uploads for MCP and CLI (Blossom protocol) (#433)
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
2026-04-30 14:35:02 -04:00
Bradley Axen
fcec10547d Route ACP observer traffic over relay (#421)
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
Co-authored-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
Co-authored-by: Tyler Longwell <tlongwell@squareup.com>
2026-04-30 09:34:37 -04:00
Wes
ddb242749f Fix mobile unread badges and read state sync (#422) 2026-04-29 07:44:44 -07:00
Bradley Axen
7c2c556d72 feat: Add local ACP session observer (#412) 2026-04-29 04:21:58 +00:00
Wes
2a01ca590d Implement NIP-RS unread state sync (#420)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-28 20:54:09 -07:00
tlongwell-block
d1c84a621c feat(nip-oa): Owner Attestation — cryptographic proof of agent authorization (#406) 2026-04-28 09:29:15 -04:00
Wes
a9024c2c78 Add multi-workspace support to desktop app (#409)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-28 04:47:11 +00:00
Wes
5a93403858 feat(pulse): rich text editor with @mentions, media uploads, and formatting (#407)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-27 13:57:46 -07:00
Wes
8abeb3d3ca feat: always notify on DM messages like Slack/Discord (#405)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-27 13:10:11 -07:00