fix(desktop): shared-compute usability — share toggle, usage indicator, model resync (#2448)

Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: Michael Neale <michael.neale@gmail.com>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
This commit is contained in:
Mic Neale
2026-07-23 23:21:01 -07:00
committed by GitHub
co-authored by Michael Neale npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d Tyler Longwell
parent c86c4f59c4
commit 9cc9652c7d
16 changed files with 1036 additions and 71 deletions
+50 -50
View File
@@ -1505,7 +1505,7 @@ version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.48.0",
]
[[package]]
@@ -1742,7 +1742,7 @@ dependencies = [
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"windows 0.62.2",
"windows 0.61.3",
]
[[package]]
@@ -3090,8 +3090,8 @@ dependencies = [
"libc",
"log",
"rustversion",
"windows-link 0.2.1",
"windows-result 0.4.1",
"windows-link 0.1.3",
"windows-result 0.3.4",
]
[[package]]
@@ -3836,7 +3836,7 @@ dependencies = [
"tokio",
"tower-service",
"tracing",
"windows-registry 0.6.1",
"windows-registry 0.5.3",
]
[[package]]
@@ -3851,7 +3851,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.62.2",
"windows-core 0.61.2",
]
[[package]]
@@ -4928,7 +4928,7 @@ dependencies = [
[[package]]
name = "mesh-llm-api-client"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"hex",
"mesh-llm-client",
@@ -4938,7 +4938,7 @@ dependencies = [
[[package]]
name = "mesh-llm-api-server"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"mesh-llm-api-client",
@@ -4949,12 +4949,12 @@ dependencies = [
[[package]]
name = "mesh-llm-build-info"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
[[package]]
name = "mesh-llm-client"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"async-trait",
@@ -4986,7 +4986,7 @@ dependencies = [
[[package]]
name = "mesh-llm-config"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"dirs",
@@ -5002,7 +5002,7 @@ dependencies = [
[[package]]
name = "mesh-llm-embedded-runtime"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"mesh-llm-host-runtime",
@@ -5012,7 +5012,7 @@ dependencies = [
[[package]]
name = "mesh-llm-events"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"clap",
@@ -5024,7 +5024,7 @@ dependencies = [
[[package]]
name = "mesh-llm-gpu-bench"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"cc",
@@ -5037,7 +5037,7 @@ dependencies = [
[[package]]
name = "mesh-llm-guardrails"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"serde",
"serde_json",
@@ -5046,7 +5046,7 @@ dependencies = [
[[package]]
name = "mesh-llm-hardware-profile"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"mesh-llm-native-runtime",
]
@@ -5054,7 +5054,7 @@ dependencies = [
[[package]]
name = "mesh-llm-host-runtime"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"argon2",
@@ -5147,7 +5147,7 @@ dependencies = [
[[package]]
name = "mesh-llm-identity"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"argon2",
"base64 0.22.1",
@@ -5169,7 +5169,7 @@ dependencies = [
[[package]]
name = "mesh-llm-native-runtime"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"serde",
@@ -5180,7 +5180,7 @@ dependencies = [
[[package]]
name = "mesh-llm-node"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"mesh-llm-types",
@@ -5194,7 +5194,7 @@ dependencies = [
[[package]]
name = "mesh-llm-plugin"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"async-trait",
@@ -5211,7 +5211,7 @@ dependencies = [
[[package]]
name = "mesh-llm-plugin-manager"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"dirs",
@@ -5229,7 +5229,7 @@ dependencies = [
[[package]]
name = "mesh-llm-protocol"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"hex",
@@ -5242,7 +5242,7 @@ dependencies = [
[[package]]
name = "mesh-llm-routing"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"iroh",
]
@@ -5250,7 +5250,7 @@ dependencies = [
[[package]]
name = "mesh-llm-runtime-install"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"dirs",
@@ -5273,7 +5273,7 @@ dependencies = [
[[package]]
name = "mesh-llm-sdk"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"mesh-llm-api-client",
@@ -5288,7 +5288,7 @@ dependencies = [
[[package]]
name = "mesh-llm-skills"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"dirs",
@@ -5299,7 +5299,7 @@ dependencies = [
[[package]]
name = "mesh-llm-system"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"chrono",
@@ -5322,7 +5322,7 @@ dependencies = [
[[package]]
name = "mesh-llm-types"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"hex",
"serde",
@@ -5333,12 +5333,12 @@ dependencies = [
[[package]]
name = "mesh-llm-ui"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
[[package]]
name = "mesh-mixture-of-agents"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"async-trait",
"mesh-llm-guardrails",
@@ -5411,7 +5411,7 @@ dependencies = [
[[package]]
name = "model-artifact"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"async-trait",
@@ -5422,7 +5422,7 @@ dependencies = [
[[package]]
name = "model-hf"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"async-trait",
@@ -5440,7 +5440,7 @@ dependencies = [
[[package]]
name = "model-package"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"bytes",
@@ -5460,7 +5460,7 @@ dependencies = [
[[package]]
name = "model-ref"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"serde",
]
@@ -5468,7 +5468,7 @@ dependencies = [
[[package]]
name = "model-resolver"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"model-artifact",
@@ -6113,7 +6113,7 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
dependencies = [
"proc-macro-crate 3.5.0",
"proc-macro-crate 1.3.1",
"proc-macro2",
"quote",
"syn 2.0.118",
@@ -6488,7 +6488,7 @@ dependencies = [
[[package]]
name = "openai-frontend"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"async-trait",
"axum",
@@ -7381,7 +7381,7 @@ version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042"
dependencies = [
"heck 0.5.0",
"heck 0.4.1",
"itertools",
"log",
"multimap",
@@ -9011,7 +9011,7 @@ checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
[[package]]
name = "skippy-cache"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"blake3",
@@ -9021,7 +9021,7 @@ dependencies = [
[[package]]
name = "skippy-coordinator"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"thiserror 2.0.18",
]
@@ -9029,7 +9029,7 @@ dependencies = [
[[package]]
name = "skippy-ffi"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"libloading 0.8.9",
]
@@ -9037,12 +9037,12 @@ dependencies = [
[[package]]
name = "skippy-metrics"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
[[package]]
name = "skippy-protocol"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"prost",
"prost-build",
@@ -9053,7 +9053,7 @@ dependencies = [
[[package]]
name = "skippy-runtime"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"libc",
@@ -9067,7 +9067,7 @@ dependencies = [
[[package]]
name = "skippy-server"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"anyhow",
"async-trait",
@@ -9095,7 +9095,7 @@ dependencies = [
[[package]]
name = "skippy-topology"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=c441ea7f328692b18b7f49ad819c7b1a603cbdcb#c441ea7f328692b18b7f49ad819c7b1a603cbdcb"
dependencies = [
"serde",
"serde_json",
@@ -11677,7 +11677,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.48.0",
]
[[package]]
@@ -12288,8 +12288,8 @@ dependencies = [
"log",
"serde",
"thiserror 2.0.18",
"windows 0.62.2",
"windows-core 0.62.2",
"windows 0.61.3",
"windows-core 0.61.2",
]
[[package]]
+6 -6
View File
@@ -90,14 +90,14 @@ buzz_persona_pkg = { package = "buzz-persona", path = "../../crates/buzz-persona
buzz_sdk_pkg = { package = "buzz-sdk", path = "../../crates/buzz-sdk" }
buzz_agent_pkg = { package = "buzz-agent", path = "../../crates/buzz-agent" }
iroh = { version = "1.0.2", optional = true }
mesh-llm-sdk = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.73.1", package = "mesh-llm-sdk", default-features = false, features = ["client", "serving"], optional = true }
mesh-llm-host-runtime = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.73.1", package = "mesh-llm-host-runtime", default-features = false, features = ["dynamic-native-runtime"], optional = true }
mesh-llm-sdk = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "c441ea7f328692b18b7f49ad819c7b1a603cbdcb", package = "mesh-llm-sdk", default-features = false, features = ["client", "serving"], optional = true }
mesh-llm-host-runtime = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "c441ea7f328692b18b7f49ad819c7b1a603cbdcb", package = "mesh-llm-host-runtime", default-features = false, features = ["dynamic-native-runtime"], optional = true }
# Model catalog + hardware survey for the Share-compute model picker (same
# diagnose pattern as mesh-console). Lib name of mesh-llm-client is mesh_client.
mesh-llm-client = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.73.1", package = "mesh-llm-client", optional = true }
mesh-llm-node = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.73.1", package = "mesh-llm-node", optional = true }
mesh-llm-system = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.73.1", package = "mesh-llm-system", optional = true }
mesh-llm-events = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.73.1", package = "mesh-llm-events", optional = true }
mesh-llm-client = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "c441ea7f328692b18b7f49ad819c7b1a603cbdcb", package = "mesh-llm-client", optional = true }
mesh-llm-node = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "c441ea7f328692b18b7f49ad819c7b1a603cbdcb", package = "mesh-llm-node", optional = true }
mesh-llm-system = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "c441ea7f328692b18b7f49ad819c7b1a603cbdcb", package = "mesh-llm-system", optional = true }
mesh-llm-events = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "c441ea7f328692b18b7f49ad819c7b1a603cbdcb", package = "mesh-llm-events", optional = true }
base64 = "0.22"
sha2 = "0.11"
tar = "0.4"
+225 -8
View File
@@ -45,6 +45,18 @@ fn load_mesh_sharing_config(app: &AppHandle) -> Result<Option<MeshSharingConfig>
const RELAY_MESH_RUNTIME_NO_TARGET: &str =
"Buzz shared compute requires a live serving member; start serving the selected model on a member, then try again";
/// Whether the Share-compute "stop sharing" path (`mesh_stop_node`) should tear
/// down the runtime currently occupying the single slot.
///
/// Serve nodes (this machine SHARING compute) are torn down. Client nodes (this
/// machine CONSUMING a peer's compute) share the same slot and MUST be left
/// running — stopping "Share compute" must never kill a consume session the
/// user didn't start from this switch.
#[cfg(feature = "mesh-llm")]
fn share_stop_should_teardown(mode: mesh_llm::MeshNodeMode) -> bool {
matches!(mode, mesh_llm::MeshNodeMode::Serve)
}
pub type CmdResult<T> = Result<T, String>;
fn advance_mesh_status_cursor(
@@ -197,19 +209,110 @@ pub async fn mesh_start_node(
/// Mesh can bind its HTTP ingress and advertise a model shortly before the
/// router has installed a usable target. Probe the exact chat path agents use
/// so startup cannot race that gap (`single target None unavailable`).
/// Which startup stage a mesh client is stuck at when it never becomes
/// inference-ready. The two live-observed failure modes are physically
/// distinct and want different user copy:
///
/// * `CatalogNeverSynced` — the local client node came up and connected to
/// the host at the control level (ping/RTT fine), but the served model
/// never appeared in the local `/v1/models` catalog. That catalog is
/// populated by the peer gossip exchange; when the gossip bi-stream can't
/// establish across the network (observed as iroh
/// `MultipathNotNegotiated` / unreachable direct path), the catalog stays
/// empty forever and every request is rejected "model not available".
/// Root cause is the network path between this machine and the host.
/// * `RoutingNeverCompleted` — the model *did* sync into the catalog, but
/// inference requests never completed (routing/transport to the host
/// failing per-request). The host is discoverable and advertised but not
/// actually serving us.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MeshReadinessFailure {
CatalogNeverSynced,
RoutingNeverCompleted,
}
/// Pure classifier: given whether the served model was ever observed in the
/// local `/v1/models` catalog during the wait, decide which stage failed.
/// Split out so the diagnosis is unit-testable without a live mesh.
fn classify_mesh_readiness_failure(model_ever_visible: bool) -> MeshReadinessFailure {
if model_ever_visible {
MeshReadinessFailure::RoutingNeverCompleted
} else {
MeshReadinessFailure::CatalogNeverSynced
}
}
/// Actionable, non-technical copy for a readiness failure. `last_detail` is the
/// last raw transport/HTTP error, appended for support triage.
fn mesh_readiness_failure_message(
failure: MeshReadinessFailure,
model_id: &str,
last_detail: &str,
) -> String {
match failure {
MeshReadinessFailure::CatalogNeverSynced => format!(
"Buzz shared compute connected to the serving member but could not sync \
the model list for \"{model_id}\" — this is a network path problem \
between this machine and the host (the compute node is reachable for \
pings but the model-sync stream did not establish). Try again, or have \
the host and this machine on a more direct network. (last: {last_detail})"
),
MeshReadinessFailure::RoutingNeverCompleted => format!(
"Buzz shared compute found \"{model_id}\" on a serving member but inference \
requests did not complete — the host is discoverable but not currently \
reachable for requests. Try again shortly. (last: {last_detail})"
),
}
}
/// Poll the local mesh OpenAI ingress until a real inference for `model_id`
/// succeeds, or a deadline elapses. On failure, returns a stage-specific,
/// actionable message (see [`MeshReadinessFailure`]) rather than a raw
/// `HTTP 429`, so the UI can tell "still warming up" apart from "can't reach
/// the host".
async fn wait_for_mesh_inference(model_id: &str) -> CmdResult<()> {
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(30))
.build()
.map_err(|error| format!("failed to build mesh readiness client: {error}"))?;
let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(120);
let models_url = format!("{}/models", crate::managed_agents::RELAY_MESH_API_BASE_URL);
let chat_url = format!(
"{}/chat/completions",
crate::managed_agents::RELAY_MESH_API_BASE_URL
);
let mut last_error = "mesh inference is not ready".to_string();
// Track whether the served model ever reached the local catalog — the
// signal that splits "catalog never synced" from "routing never completed".
let mut model_ever_visible = false;
while tokio::time::Instant::now() < deadline {
// Refresh catalog visibility. "auto" delegates model choice to the
// router, so any advertised model counts as the catalog having synced.
if let Ok(response) = client
.get(&models_url)
.bearer_auth(crate::managed_agents::RELAY_MESH_API_KEY_PLACEHOLDER)
.send()
.await
{
if let Ok(body) = response.json::<serde_json::Value>().await {
if let Some(data) = body.get("data").and_then(|d| d.as_array()) {
let wanted = model_id.trim().replace("@main", "");
let visible = !data.is_empty()
&& (model_id == crate::mesh_llm::AUTO_MODEL_ID
|| data.iter().any(|m| {
m.get("id")
.and_then(|id| id.as_str())
.map(|id| id.replace("@main", "") == wanted)
.unwrap_or(false)
}));
model_ever_visible |= visible;
}
}
}
match client
.post(format!(
"{}/chat/completions",
crate::managed_agents::RELAY_MESH_API_BASE_URL
))
.post(&chat_url)
.bearer_auth(crate::managed_agents::RELAY_MESH_API_KEY_PLACEHOLDER)
.json(&serde_json::json!({
"model": model_id,
@@ -230,8 +333,12 @@ async fn wait_for_mesh_inference(model_id: &str) -> CmdResult<()> {
}
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
}
Err(format!(
"Buzz shared compute did not become inference-ready for {model_id}: {last_error}"
let failure = classify_mesh_readiness_failure(model_ever_visible);
Err(mesh_readiness_failure_message(
failure,
model_id,
&last_error,
))
}
@@ -411,8 +518,22 @@ pub async fn mesh_stop_node(
app: AppHandle,
state: State<'_, AppState>,
) -> CmdResult<mesh_llm::MeshNodeStatus> {
let runtime = state.mesh_llm_runtime.lock().await.take();
if let Some(runtime) = runtime {
// The single runtime slot is shared by serve (this machine SHARING
// compute) and client (this machine CONSUMING a peer's compute) roles.
// Stopping "Share compute" must NEVER tear down a client node: inspect the
// role under the lock and, when it's a consume session, leave it running
// and return its live status unchanged. The frontend also guards this, but
// status can be stale between polls, so the backend is authoritative.
let taken = {
let mut guard = state.mesh_llm_runtime.lock().await;
if let Some(runtime) = guard.as_ref() {
if !share_stop_should_teardown(runtime.mode()) {
return runtime.status().await.map_err(|error| error.to_string());
}
}
guard.take()
};
if let Some(runtime) = taken {
runtime.stop().await.map_err(|error| error.to_string())?;
}
save_mesh_sharing_config(
@@ -436,6 +557,20 @@ pub async fn mesh_node_status(state: State<'_, AppState>) -> CmdResult<mesh_llm:
}
}
/// Read-only host-side usage: who/what is using the compute this machine is
/// sharing. Returns a zeroed snapshot when no runtime is active. No new trust
/// surface — it reads the serving node's own runtime metrics.
#[tauri::command]
pub async fn mesh_serving_usage(
state: State<'_, AppState>,
) -> CmdResult<mesh_llm::MeshServingUsage> {
let runtime = state.mesh_llm_runtime.lock().await;
match runtime.as_ref() {
Some(runtime) => runtime.serving_usage().await.map_err(|e| e.to_string()),
None => Ok(mesh_llm::MeshServingUsage::default()),
}
}
#[tauri::command]
pub async fn mesh_installed_models(
state: State<'_, AppState>,
@@ -479,6 +614,42 @@ mod tests {
}
}
#[test]
fn readiness_failure_is_catalog_sync_when_model_never_visible() {
assert_eq!(
classify_mesh_readiness_failure(false),
MeshReadinessFailure::CatalogNeverSynced
);
}
#[test]
fn readiness_failure_is_routing_when_model_was_visible() {
assert_eq!(
classify_mesh_readiness_failure(true),
MeshReadinessFailure::RoutingNeverCompleted
);
}
#[test]
fn readiness_messages_are_distinct_and_actionable() {
let catalog = mesh_readiness_failure_message(
MeshReadinessFailure::CatalogNeverSynced,
"auto",
"HTTP 429",
);
let routing = mesh_readiness_failure_message(
MeshReadinessFailure::RoutingNeverCompleted,
"auto",
"HTTP 503",
);
// Distinct diagnoses, each names the model and carries the raw detail.
assert_ne!(catalog, routing);
assert!(catalog.contains("network path"));
assert!(catalog.contains("HTTP 429"));
assert!(routing.contains("did not complete"));
assert!(routing.contains("HTTP 503"));
}
#[test]
fn mesh_status_cursor_uses_relay_composite_tiebreak() {
let event = nostr::EventBuilder::new(nostr::Kind::TextNote, "status")
@@ -546,6 +717,52 @@ mod tests {
assert_eq!(pick_serve_target_for_model(targets, "model-missing"), None);
}
#[test]
fn share_stop_tears_down_serve_but_not_client() {
// Stopping "Share compute" tears down a serve node (we were sharing)
// but must leave a client node alone (we are consuming a peer). This is
// the backend half of the toggle-on regression: a client node occupies
// the single slot and reports state:"running", and the stop path must
// not kill it.
assert!(
share_stop_should_teardown(mesh_llm::MeshNodeMode::Serve),
"serve node is our sharing runtime; stop must tear it down"
);
assert!(
!share_stop_should_teardown(mesh_llm::MeshNodeMode::Client),
"client node is a consume session; stop must NOT tear it down"
);
}
#[test]
fn client_status_serializes_with_running_state_and_client_mode() {
// Contract pin for the TS mock (e2eBridge.ts) and the frontend
// predicate: a consuming node serializes as
// {"state":"running","mode":"client"}. If serde renaming drifts, the
// hand-written mock shape and `deriveMeshShareToggle` would silently
// stop matching the real IPC payload.
let status = mesh_llm::MeshNodeStatus {
state: mesh_llm::MeshNodeState::Running,
mode: Some(mesh_llm::MeshNodeMode::Client),
// `MeshHealth::ok()` is module-private; build via the public fields.
health: mesh_llm::MeshHealth {
status: mesh_llm::MeshHealthStatus::Ok,
reason: None,
},
api_base_url: Some("http://127.0.0.1:9337/v1".to_string()),
console_url: None,
model_id: None,
model_name: None,
invite_token: None,
endpoint_id: None,
device_id: None,
device_name: None,
};
let value = serde_json::to_value(&status).expect("serialize mesh status");
assert_eq!(value["state"], serde_json::json!("running"));
assert_eq!(value["mode"], serde_json::json!("client"));
}
#[tokio::test]
async fn cold_client_preflight_requires_explicit_target() {
let state = build_app_state();
+1
View File
@@ -799,6 +799,7 @@ pub fn run() {
mesh_start_node,
mesh_stop_node,
mesh_node_status,
mesh_serving_usage,
mesh_installed_models,
mesh_model_catalog,
update_managed_agent,
+88
View File
@@ -196,6 +196,80 @@ pub struct MeshNodeStatus {
pub device_name: Option<String>,
}
/// Host-side "who is using the compute I'm sharing" snapshot.
///
/// Read-only projection of the serving node's own runtime metrics (the same
/// `routing_metrics` / `inflight_requests` the SDK already exposes on the local
/// console). No new trust surface: it reads the node's own status payload.
///
/// The local/remote/endpoint attempt split is what distinguishes *my own*
/// agent (local) from *another member consuming my compute* (remote/endpoint).
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
pub struct MeshServingUsage {
/// Requests being served right now.
pub inflight: u64,
/// Highest concurrent in-flight seen this session.
pub peak_inflight: u64,
/// Total requests routed through this node.
pub requests_served: u64,
/// Completion tokens produced.
pub tokens_served: u64,
/// Recent decode throughput.
pub tokens_per_second: f64,
/// Requests served for this machine's own agents.
pub local_attempts: u64,
/// Requests served for a remote peer (someone else consuming my compute).
pub remote_attempts: u64,
/// Requests served via an advertised endpoint (also a remote consumer).
pub endpoint_attempts: u64,
/// Other nodes currently visible as peers.
pub peers: u64,
}
impl MeshServingUsage {
/// True when at least one request has been served for a non-local consumer.
pub fn has_remote_consumers(&self) -> bool {
self.remote_attempts > 0 || self.endpoint_attempts > 0
}
}
/// Pure extractor: project a raw SDK status payload into [`MeshServingUsage`].
///
/// Every field is read defensively (missing → 0) so an SDK shape change
/// degrades to "no usage shown" rather than an error. Kept pure so it can be
/// unit-tested against a captured payload without a live runtime.
pub fn serving_usage_from_payload(payload: &serde_json::Value) -> MeshServingUsage {
let u64_at = |v: &serde_json::Value| v.as_u64().unwrap_or(0);
let rm = payload.get("routing_metrics");
let local = rm.and_then(|m| m.get("local_node"));
let get_u64 = |obj: Option<&serde_json::Value>, key: &str| {
obj.and_then(|o| o.get(key)).map(u64_at).unwrap_or(0)
};
MeshServingUsage {
inflight: local
.and_then(|l| l.get("current_inflight_requests"))
.map(u64_at)
.or_else(|| payload.get("inflight_requests").map(u64_at))
.unwrap_or(0),
peak_inflight: get_u64(local, "peak_inflight_requests"),
requests_served: get_u64(rm, "request_count"),
tokens_served: get_u64(rm, "completion_tokens_observed"),
tokens_per_second: rm
.and_then(|m| m.get("avg_tokens_per_second"))
.and_then(serde_json::Value::as_f64)
.unwrap_or(0.0),
local_attempts: get_u64(local, "local_attempt_count"),
remote_attempts: get_u64(local, "remote_attempt_count"),
endpoint_attempts: get_u64(local, "endpoint_attempt_count"),
peers: payload
.get("peers")
.and_then(serde_json::Value::as_array)
.map(|a| a.len() as u64)
.unwrap_or(0),
}
}
pub fn stopped_status() -> MeshNodeStatus {
MeshNodeStatus {
state: MeshNodeState::Off,
@@ -381,6 +455,14 @@ impl DesktopMeshRuntime {
&self.start_request
}
/// The role this runtime was started in. Serve = this machine is SHARING
/// compute; Client = this machine is CONSUMING a peer's compute. Both
/// occupy the single runtime slot, so callers that act only on the sharing
/// role (e.g. the Share-compute stop path) must check this first.
pub fn mode(&self) -> MeshNodeMode {
self.mode
}
pub async fn status(&self) -> anyhow::Result<MeshNodeStatus> {
let status = self.handle.status().await?;
self.status_from_sdk(status)
@@ -420,6 +502,12 @@ impl DesktopMeshRuntime {
Ok(payload)
}
/// Read-only host-side usage snapshot from the node's own runtime metrics.
pub async fn serving_usage(&self) -> anyhow::Result<MeshServingUsage> {
let status = self.handle.status().await?;
Ok(serving_usage_from_payload(&status.payload))
}
pub async fn dial_endpoint_addr(&self, endpoint_addr: impl Into<String>) -> anyhow::Result<()> {
let endpoint_addr = endpoint_addr.into();
let validated = validate_advertised_endpoint(&endpoint_addr)?;
@@ -597,3 +597,69 @@ fn owner_roster_without_membership_list_fails_closed() {
assert!(super::owner_ids_from_events(&events).is_empty());
}
#[test]
fn serving_usage_extracts_local_and_remote_attempts() {
// Captured shape from a live serving node's status payload. The local vs
// remote/endpoint split is what tells "my own agent" apart from "a peer
// consuming my compute".
let payload = json!({
"inflight_requests": 0,
"peers": [],
"routing_metrics": {
"request_count": 5,
"completion_tokens_observed": 764,
"avg_tokens_per_second": 29.651,
"local_node": {
"current_inflight_requests": 1,
"peak_inflight_requests": 2,
"local_attempt_count": 4,
"remote_attempt_count": 0,
"endpoint_attempt_count": 0
}
}
});
let usage = super::serving_usage_from_payload(&payload);
assert_eq!(usage.inflight, 1);
assert_eq!(usage.peak_inflight, 2);
assert_eq!(usage.requests_served, 5);
assert_eq!(usage.tokens_served, 764);
assert_eq!(usage.local_attempts, 4);
assert_eq!(usage.remote_attempts, 0);
assert_eq!(usage.endpoint_attempts, 0);
assert!(
!usage.has_remote_consumers(),
"all-local traffic is not a remote consumer"
);
}
#[test]
fn serving_usage_flags_remote_consumer() {
let payload = json!({
"peers": [{"id": "a"}, {"id": "b"}],
"routing_metrics": {
"request_count": 10,
"local_node": {
"local_attempt_count": 3,
"remote_attempt_count": 6,
"endpoint_attempt_count": 1
}
}
});
let usage = super::serving_usage_from_payload(&payload);
assert_eq!(usage.remote_attempts, 6);
assert_eq!(usage.endpoint_attempts, 1);
assert_eq!(usage.peers, 2);
assert!(
usage.has_remote_consumers(),
"remote/endpoint attempts mean someone else is using my compute"
);
}
#[test]
fn serving_usage_defaults_to_zero_on_missing_fields() {
// SDK shape drift must degrade to "no usage" not panic.
let usage = super::serving_usage_from_payload(&json!({}));
assert_eq!(usage, super::MeshServingUsage::default());
assert!(!usage.has_remote_consumers());
}
+5
View File
@@ -26,6 +26,11 @@ pub async fn mesh_node_status(_state: State<'_, AppState>) -> CmdResult<serde_js
Err("mesh-llm feature not enabled".to_string())
}
#[tauri::command]
pub async fn mesh_serving_usage(_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>,
@@ -0,0 +1,43 @@
import * as React from "react";
import { meshServingUsage } from "@/shared/api/tauriMesh";
import type { MeshServingUsage } from "@/shared/api/tauriMesh";
/**
* Polls host-side serving usage while this machine is sharing compute.
*
* Only polls when `enabled` (the card passes `isSharing`) so a machine that
* isn't serving does no work. Cadence is a plain 4s — usage is informational,
* not a lifecycle transition, so it doesn't need the adaptive stepping that
* `useMeshNodeStatus` uses. Returns `null` until the first successful fetch.
*/
export function useMeshServingUsage(enabled: boolean): MeshServingUsage | null {
const [usage, setUsage] = React.useState<MeshServingUsage | null>(null);
React.useEffect(() => {
if (!enabled) {
setUsage(null);
return;
}
let cancelled = false;
const fetchOnce = () => {
(async () => {
try {
const value = await meshServingUsage();
if (!cancelled) setUsage(value);
} catch {
// Usage is best-effort; a failed poll leaves the last value in place
// rather than flapping the indicator.
}
})();
};
fetchOnce();
const handle = window.setInterval(fetchOnce, 4000);
return () => {
cancelled = true;
window.clearInterval(handle);
};
}, [enabled]);
return usage;
}
@@ -0,0 +1,86 @@
import assert from "node:assert/strict";
import { test } from "node:test";
import { deriveServingIndicator } from "./servingUsage.ts";
function usage(overrides = {}) {
return {
inflight: 0,
peakInflight: 0,
requestsServed: 0,
tokensServed: 0,
tokensPerSecond: 0,
localAttempts: 0,
remoteAttempts: 0,
endpointAttempts: 0,
peers: 0,
...overrides,
};
}
test("hidden when not sharing", () => {
const i = deriveServingIndicator(usage({ requestsServed: 5 }), false);
assert.equal(i.show, false);
});
test("hidden when usage not yet fetched", () => {
const i = deriveServingIndicator(null, true);
assert.equal(i.show, false);
});
test("sharing but nothing served yet -> idle, no detail", () => {
const i = deriveServingIndicator(usage(), true);
assert.equal(i.show, true);
assert.equal(i.active, false);
assert.equal(i.hasRemoteConsumers, false);
assert.match(i.label, /Idle/);
});
test("only local agent traffic -> not a remote consumer", () => {
const i = deriveServingIndicator(
usage({ requestsServed: 4, localAttempts: 4, tokensPerSecond: 30 }),
true,
);
assert.equal(i.hasRemoteConsumers, false);
assert.match(i.label, /Idle/); // served earlier, none live now
assert.match(i.detail, /4 requests served/);
});
test("local agent live now -> serving your agent", () => {
const i = deriveServingIndicator(
usage({ inflight: 1, localAttempts: 2, tokensPerSecond: 28 }),
true,
);
assert.equal(i.active, true);
assert.equal(i.hasRemoteConsumers, false);
assert.match(i.label, /Serving your agent/);
assert.match(i.label, /1 live/);
});
test("remote consumer, not live -> used by another member (headline case)", () => {
const i = deriveServingIndicator(
usage({
requestsServed: 7,
remoteAttempts: 6,
endpointAttempts: 1,
peers: 2,
}),
true,
);
assert.equal(i.hasRemoteConsumers, true);
assert.equal(i.active, false);
assert.match(i.label, /another member/);
assert.match(i.label, /7 requests/); // remote+endpoint = 7
assert.match(i.detail, /2 peers/);
});
test("remote consumer live now -> in use now, singular peer/request grammar", () => {
const i = deriveServingIndicator(
usage({ inflight: 1, remoteAttempts: 1, peers: 1, tokensPerSecond: 31 }),
true,
);
assert.equal(i.active, true);
assert.equal(i.hasRemoteConsumers, true);
assert.match(i.label, /In use now by another member/);
assert.match(i.detail, /1 peer\b/); // singular
});
@@ -0,0 +1,101 @@
import type { MeshServingUsage } from "@/shared/api/tauriMesh";
/**
* Pure projection of host-side serving usage into a small, politely-worded
* indicator model for the Share compute card.
*
* Single source of truth for "who is using the compute I'm sharing" copy, so
* the component and its tests agree. Kept pure/total (accepts null = not yet
* fetched) and defensive (all fields optional-safe via the Rust extractor).
*
* Distinctions that matter:
* - `localAttempts` = this machine's OWN agents using the local model. Not a
* "someone else is here" signal — surfaced softly as activity, not as a peer.
* - `remoteAttempts` / `endpointAttempts` = another member consuming this
* machine's compute. THIS is the "someone connected to what I'm sharing"
* signal.
*/
export type MeshServingIndicator = {
/** Whether to show anything at all (only while actively sharing). */
show: boolean;
/** Someone is being served right now. */
active: boolean;
/** A non-local member is (or has been) consuming this machine's compute. */
hasRemoteConsumers: boolean;
/** One-line status suitable for the card. */
label: string;
/** Longer detail for a tooltip / secondary line. */
detail: string | null;
};
function plural(n: number, one: string, many = `${one}s`): string {
return n === 1 ? one : many;
}
/**
* @param usage latest snapshot from `meshServingUsage`, or null if not fetched
* @param isSharing whether this machine is currently in serve mode (card owns
* this from the toggle model). Usage is only meaningful while
* sharing.
*/
export function deriveServingIndicator(
usage: MeshServingUsage | null,
isSharing: boolean,
): MeshServingIndicator {
const hidden: MeshServingIndicator = {
show: false,
active: false,
hasRemoteConsumers: false,
label: "",
detail: null,
};
if (!isSharing || !usage) {
return hidden;
}
const hasRemoteConsumers =
usage.remoteAttempts > 0 || usage.endpointAttempts > 0;
const active = usage.inflight > 0;
// Remote consumer present (or seen) — the headline case the user asked for.
if (hasRemoteConsumers) {
const remote = usage.remoteAttempts + usage.endpointAttempts;
const label = active
? `In use now by another member · ${usage.inflight} live`
: `Used by another member · ${remote} ${plural(remote, "request")}`;
const detail =
usage.peers > 0
? `${usage.peers} ${plural(usage.peers, "peer")} on the mesh · ${Math.round(usage.tokensPerSecond)} tok/s`
: `${Math.round(usage.tokensPerSecond)} tok/s`;
return { show: true, active, hasRemoteConsumers: true, label, detail };
}
// Only local (this machine's own agents) — show softly as activity.
if (active) {
return {
show: true,
active: true,
hasRemoteConsumers: false,
label: `Serving your agent · ${usage.inflight} live`,
detail: `${Math.round(usage.tokensPerSecond)} tok/s`,
};
}
if (usage.requestsServed > 0) {
return {
show: true,
active: false,
hasRemoteConsumers: false,
label: "Idle · no one using it right now",
detail: `${usage.requestsServed} ${plural(usage.requestsServed, "request")} served this session`,
};
}
// Sharing but nothing served yet.
return {
show: true,
active: false,
hasRemoteConsumers: false,
label: "Idle · no one using it yet",
detail: null,
};
}
@@ -0,0 +1,90 @@
import assert from "node:assert/strict";
import test from "node:test";
import { deriveMeshShareToggle } from "./shareToggleState.ts";
const status = (overrides = {}) => ({
state: "off",
mode: null,
health: { status: "ok", reason: null },
apiBaseUrl: null,
consoleUrl: null,
modelId: null,
modelName: null,
...overrides,
});
test("serve-mode running/starting reads as sharing", () => {
for (const state of ["running", "starting"]) {
const model = deriveMeshShareToggle(status({ state, mode: "serve" }));
assert.equal(model.isSharing, true, `serve+${state} should be sharing`);
assert.equal(model.isConsuming, false);
assert.equal(model.slotOccupied, true);
}
});
test("client-mode running/starting is consuming, NOT sharing (regression)", () => {
// The core bug: consuming a peer's compute starts a client node in the same
// slot, which reports state:"running". The Share toggle must stay off.
for (const state of ["running", "starting"]) {
const model = deriveMeshShareToggle(status({ state, mode: "client" }));
assert.equal(
model.isSharing,
false,
`client+${state} must NOT light the Share toggle`,
);
assert.equal(model.isConsuming, true);
assert.equal(model.slotOccupied, true);
}
});
test("a FAILED serve node still occupies the slot and stays turn-off-able", () => {
// Expert review: a failed runtime still holds the single slot, so a fresh
// start would throw "already running". It must read as sharing (so the
// switch stays on and can be turned OFF to clear/retry) and occupy the slot.
const model = deriveMeshShareToggle(
status({ state: "failed", mode: "serve" }),
);
assert.equal(
model.isSharing,
true,
"failed serve node is still turn-off-able",
);
assert.equal(model.slotOccupied, true);
});
test("a FAILED client node occupies the slot but is not sharing", () => {
const model = deriveMeshShareToggle(
status({ state: "failed", mode: "client" }),
);
assert.equal(model.isSharing, false);
assert.equal(model.isConsuming, true);
assert.equal(model.slotOccupied, true);
});
test("off / stopping never occupy the slot or read as sharing/consuming", () => {
for (const state of ["off", "stopping"]) {
for (const mode of [null, "serve", "client"]) {
const model = deriveMeshShareToggle(status({ state, mode }));
assert.equal(model.isSharing, false, `${mode}+${state} not sharing`);
assert.equal(model.isConsuming, false, `${mode}+${state} not consuming`);
assert.equal(model.slotOccupied, false, `${mode}+${state} slot free`);
}
}
});
test("null status (not yet fetched) is neither sharing nor consuming", () => {
const model = deriveMeshShareToggle(null);
assert.equal(model.isSharing, false);
assert.equal(model.isConsuming, false);
assert.equal(model.slotOccupied, false);
});
test("running with a missing mode occupies the slot but is not sharing", () => {
// Defensive: a status that somehow lacks mode must not default to "on", but
// it DOES hold the slot (a fresh start would fail), so it stays occupied.
const model = deriveMeshShareToggle(status({ state: "running", mode: null }));
assert.equal(model.isSharing, false);
assert.equal(model.isConsuming, false);
assert.equal(model.slotOccupied, true);
});
@@ -0,0 +1,64 @@
import type { MeshNodeStatus } from "@/shared/api/tauriMesh";
/**
* Derived Share-compute toggle model.
*
* The single mesh runtime slot is shared by BOTH roles: serve mode (this
* machine SHARING compute) and client mode (this machine CONSUMING a peer's
* compute). Both report `state: "running"`. The Share toggle must therefore
* key off `mode`, not `state` alone — otherwise consuming a peer's compute
* lights up the Share switch and (worse) clicking it tears down the unrelated
* client session. See `deriveMeshShareToggle`.
*/
export type MeshShareToggleModel = {
/**
* The Share switch is on: a serve-mode node occupies the slot. Stays true
* while it is starting or even if it later fails health (the runtime still
* occupies the slot, and the user must be able to turn it off to clear/retry
* — see `StatusLine` for the health sub-state). A serve node that also routes
* a peer's model is still sharing: routing is a capability, not a role.
*/
isSharing: boolean;
/**
* A client-mode runtime occupies the single slot (this machine is consuming
* a peer's compute). The Share switch must read off + disabled while true.
*/
isConsuming: boolean;
/**
* ANY runtime occupies the single slot (serve or client, healthy or failed).
* A fresh `mesh_start_node` fails with "already running" while true, so the
* switch must not offer a start — only a stop of an existing serve node.
*/
slotOccupied: boolean;
};
/**
* A runtime object occupies the slot once it is starting or running — and also
* when it has `failed` (it started, then errored; the runtime is still in the
* slot and blocks a fresh start). `off`/`stopping` do not occupy it.
*/
function occupiesSlot(status: MeshNodeStatus | null): boolean {
return (
status?.state === "running" ||
status?.state === "starting" ||
status?.state === "failed"
);
}
/**
* Project a mesh node status into the Share toggle's on/consuming state.
*
* Pure and total (accepts `null` = status not yet fetched). This is the single
* source of truth for "is this machine sharing?" — the component and its
* regression tests both consume it so the `state`-only bug cannot come back.
*/
export function deriveMeshShareToggle(
status: MeshNodeStatus | null,
): MeshShareToggleModel {
const occupied = occupiesSlot(status);
return {
isSharing: occupied && status?.mode === "serve",
isConsuming: occupied && status?.mode === "client",
slotOccupied: occupied,
};
}
@@ -29,6 +29,9 @@ import {
useMeshDownloadProgress,
} from "../hooks/useMeshDownloadProgress";
import { useMeshNodeStatus } from "../hooks/useMeshNodeStatus";
import { useMeshServingUsage } from "../hooks/useMeshServingUsage";
import { deriveMeshShareToggle } from "../shareToggleState";
import { deriveServingIndicator } from "../servingUsage";
const MODEL_DRAFT_STORAGE_KEY = "buzz.mesh-compute.share.model.v1";
const MAX_VRAM_DRAFT_STORAGE_KEY = "buzz.mesh-compute.share.max-vram-gb.v1";
@@ -122,16 +125,35 @@ export function MeshComputeSettingsCard() {
}, []);
// Mirror the running node's modelId back into the field so the card shows
// what's actually being served, even after a fresh app load.
// what's ACTUALLY being served — not just on a fresh load (empty field) but
// whenever the served model differs from the field (e.g. the member switched
// models and the node restarted on the new one). This is safe from clobbering
// a mid-edit because the field is disabled whenever the slot is occupied
// (`controlsDisabled`), so the user can't be typing while a node is running.
React.useEffect(() => {
if (status?.state === "running" && status.modelId && modelInput === "") {
if (
status?.state === "running" &&
status.modelId &&
status.modelId !== modelInput
) {
setModelInput(status.modelId);
writeDraft(MODEL_DRAFT_STORAGE_KEY, status.modelId);
}
}, [status?.state, status?.modelId, modelInput]);
const isOn = status?.state === "running" || status?.state === "starting";
const controlsDisabled = isOn || actionInFlight;
// The Share toggle reflects ONLY serve-mode occupancy. A client-mode runtime
// (this machine consuming a peer's compute) shares the single runtime slot
// and also reports state:"running" — but it must NOT light this switch, and
// toggling off must never tear down that unrelated consume session.
const { isSharing, isConsuming, slotOccupied } =
deriveMeshShareToggle(status);
// Host-side "who is using the compute I'm sharing" — only polled while
// actively sharing (serve mode). Read-only; reads the node's own metrics.
const servingUsage = useMeshServingUsage(isSharing);
const servingIndicator = deriveServingIndicator(servingUsage, isSharing);
// Any occupying runtime (serve or client, healthy or failed) locks the model
// inputs and blocks a fresh start — stop it before reconfiguring.
const controlsDisabled = slotOccupied || actionInFlight;
const refClass = classifyModelRef(modelInput);
const canStart =
refClass.kind !== "unknown" &&
@@ -139,6 +161,13 @@ export function MeshComputeSettingsCard() {
status?.state !== "starting";
async function handleToggle(next: boolean) {
// Never let the Share switch tear down a consume session. The switch is
// already disabled while consuming, but status can be stale between polls,
// so refuse a stop that isn't stopping OUR serve node as a belt-and-braces
// guard (the backend enforces this authoritatively too).
if (!next && !isSharing) {
return;
}
setActionError(null);
setPendingAction(next ? "start" : "stop");
setActionInFlight(true);
@@ -202,12 +231,43 @@ export function MeshComputeSettingsCard() {
>
Share this machine
</label>
<StatusLine pendingAction={pendingAction} status={status} />
<StatusLine
isConsuming={isConsuming}
pendingAction={pendingAction}
status={status}
/>
{servingIndicator.show ? (
<p
className={
servingIndicator.hasRemoteConsumers
? "mt-0.5 text-2xs text-emerald-600 dark:text-emerald-400"
: "mt-0.5 text-2xs text-muted-foreground"
}
data-testid="mesh-serving-usage"
title={servingIndicator.detail ?? undefined}
>
{servingIndicator.label}
{servingIndicator.detail ? (
<span className="text-muted-foreground">
{" "}
· {servingIndicator.detail}
</span>
) : null}
</p>
) : null}
</div>
<Switch
checked={isOn}
checked={isSharing}
data-testid="mesh-share-compute-toggle"
disabled={actionInFlight || (!isOn && !canStart)}
disabled={
// When the slot is occupied, the switch is only actionable if
// WE are sharing (so it can stop a serve node — even a failed
// one). Any other occupant (consuming, or an unexpected
// modeless-running node) would make a fresh start throw "already
// running", so keep it disabled. When the slot is empty, gate on
// a valid model ref.
actionInFlight || (slotOccupied ? !isSharing : !canStart)
}
id="mesh-share-compute-toggle"
onCheckedChange={handleToggle}
/>
@@ -488,9 +548,11 @@ function CatalogPicker({
}
function StatusLine({
isConsuming,
pendingAction,
status,
}: {
isConsuming: boolean;
pendingAction: "start" | "stop" | null;
status: MeshNodeStatus | null;
}) {
@@ -500,6 +562,18 @@ function StatusLine({
if (pendingAction === "stop") {
return <p className="text-sm text-muted-foreground">Stopping…</p>;
}
// A client-mode runtime owns the single slot: this machine is consuming a
// peer's compute, not sharing. Explain why Share is off + disabled instead
// of showing the misleading "Sharing … with relay members" serve copy. The
// client node is app-session-lived infra with no user stop control, so the
// copy states the fact rather than promising an action that doesn't exist.
if (isConsuming) {
return (
<p className="text-sm text-muted-foreground">
This machine is currently using another member's shared compute.
</p>
);
}
if (!status) {
return <p className="text-sm text-muted-foreground">Checking status…</p>;
}
+21
View File
@@ -52,6 +52,27 @@ export async function meshNodeStatus(): Promise<MeshNodeStatus> {
return await invokeTauri<MeshNodeStatus>("mesh_node_status");
}
/**
* Host-side usage of the compute this machine is sharing. The
* local/remote/endpoint attempt split distinguishes this machine's own agents
* (local) from another member consuming this machine's compute (remote/endpoint).
*/
export type MeshServingUsage = {
inflight: number;
peakInflight: number;
requestsServed: number;
tokensServed: number;
tokensPerSecond: number;
localAttempts: number;
remoteAttempts: number;
endpointAttempts: number;
peers: number;
};
export async function meshServingUsage(): Promise<MeshServingUsage> {
return await invokeTauri<MeshServingUsage>("mesh_serving_usage");
}
export async function meshInstalledModels(): Promise<MeshModelOption[]> {
return await invokeTauri<MeshModelOption[]>("mesh_installed_models");
}
+64
View File
@@ -1049,6 +1049,26 @@ declare global {
admitted?: boolean;
models?: Array<{ id: string; name: string | null }>;
denyReason?: string;
/** Seed the runtime slot's lifecycle state (default "off"). */
nodeState?: "off" | "running";
/**
* Seed the runtime slot's role. "client" models this machine CONSUMING a
* peer's compute — it shares the single slot and reports state:"running",
* so the Share toggle must stay off. Drives the toggle-on regression test.
*/
nodeMode?: "serve" | "client" | null;
/** Seed host-side serving usage to exercise the "who's using my compute" indicator. */
servingUsage?: Partial<{
inflight: number;
peakInflight: number;
requestsServed: number;
tokensServed: number;
tokensPerSecond: number;
localAttempts: number;
remoteAttempts: number;
endpointAttempts: number;
peers: number;
}>;
}) => void;
__BUZZ_E2E_SEED_ACTIVE_TURNS__?: (input: {
agentPubkey: string;
@@ -2762,12 +2782,37 @@ function resetMockSaveSubscriptions(config: E2eConfig | undefined) {
// in a browser. They deliberately do NOT model real admission, real inference,
// or real mesh routing — those are proven by the Rust layer-2 tests and the
// on-hardware layer-1 example. Do not port any of this into production code.
type MockServingUsage = {
inflight: number;
peakInflight: number;
requestsServed: number;
tokensServed: number;
tokensPerSecond: number;
localAttempts: number;
remoteAttempts: number;
endpointAttempts: number;
peers: number;
};
const ZERO_SERVING_USAGE: MockServingUsage = {
inflight: 0,
peakInflight: 0,
requestsServed: 0,
tokensServed: 0,
tokensPerSecond: 0,
localAttempts: 0,
remoteAttempts: 0,
endpointAttempts: 0,
peers: 0,
};
const mockMeshState: {
admitted: boolean;
models: Array<{ id: string; name: string | null }>;
denyReason: string;
nodeState: "off" | "running";
nodeMode: "serve" | "client" | null;
servingUsage: MockServingUsage;
} = {
admitted: true,
models: [
@@ -2776,6 +2821,7 @@ const mockMeshState: {
denyReason: "not a relay member",
nodeState: "off",
nodeMode: null,
servingUsage: { ...ZERO_SERVING_USAGE },
};
function resetMockMesh() {
@@ -2786,6 +2832,7 @@ function resetMockMesh() {
mockMeshState.denyReason = "not a relay member";
mockMeshState.nodeState = "off";
mockMeshState.nodeMode = null;
mockMeshState.servingUsage = { ...ZERO_SERVING_USAGE };
}
let mockPersonas: RawPersona[] = [];
let mockTeams: RawTeam[] = [];
@@ -9110,6 +9157,13 @@ export function maybeInstallE2eTauriMocks() {
if (mesh.models !== undefined) mockMeshState.models = mesh.models;
if (mesh.denyReason !== undefined)
mockMeshState.denyReason = mesh.denyReason;
if (mesh.nodeState !== undefined) mockMeshState.nodeState = mesh.nodeState;
if (mesh.nodeMode !== undefined) mockMeshState.nodeMode = mesh.nodeMode;
if (mesh.servingUsage !== undefined)
mockMeshState.servingUsage = {
...mockMeshState.servingUsage,
...mesh.servingUsage,
};
};
let seedTurnSeq = Date.now();
window.__BUZZ_E2E_SEED_ACTIVE_TURNS__ = ({
@@ -9229,6 +9283,8 @@ export function maybeInstallE2eTauriMocks() {
return mockMeshState.models;
case "mesh_node_status":
return meshNodeStatus(mockMeshState.nodeState, mockMeshState.nodeMode);
case "mesh_serving_usage":
return mockMeshState.servingUsage;
case "mesh_start_node": {
const req = (
payload as { request?: { mode?: "serve" | "client" } } | null
@@ -9238,6 +9294,14 @@ export function maybeInstallE2eTauriMocks() {
return meshNodeStatus(mockMeshState.nodeState, mockMeshState.nodeMode);
}
case "mesh_stop_node":
// Mirror the backend contract: "stop sharing" must never tear down a
// client (consume) node occupying the single slot. Leave it running.
if (mockMeshState.nodeMode === "client") {
return meshNodeStatus(
mockMeshState.nodeState,
mockMeshState.nodeMode,
);
}
mockMeshState.nodeState = "off";
mockMeshState.nodeMode = null;
return meshNodeStatus("off", null);
+45
View File
@@ -5,6 +5,10 @@ import { openSettings } from "../helpers/settings";
type E2eWindow = Window & {
__BUZZ_E2E_COMMANDS__?: string[];
__BUZZ_E2E_SET_MESH__?: (mesh: {
nodeState?: "off" | "running";
nodeMode?: "serve" | "client" | null;
}) => void;
};
test.beforeEach(async ({ page }) => {
@@ -51,3 +55,44 @@ test("Share compute has a clear empty state and starts and stops sharing", async
)
.toContain("mesh_stop_node");
});
test("consuming a peer's compute does NOT light the Share toggle", async ({
page,
}) => {
// Regression: consuming someone else's shared compute starts a client-mode
// node in the single runtime slot, which reports state:"running". The Share
// toggle keyed off state alone and lit up — and clicking it would have torn
// down the unrelated consume session. It must stay off + disabled, explain
// why, and issue no stop command.
await page.goto("/");
// The mesh seed hook is installed when the mock bridge boots; calling it
// before then silently no-ops (optional chaining) and the seed is lost.
await page.waitForFunction(
() => typeof (window as E2eWindow).__BUZZ_E2E_SET_MESH__ === "function",
);
await page.evaluate(() => {
(window as E2eWindow).__BUZZ_E2E_SET_MESH__?.({
nodeState: "running",
nodeMode: "client",
});
});
await openSettings(page, "compute");
const card = page.getByTestId("settings-mesh-share-compute");
const toggle = page.getByTestId("mesh-share-compute-toggle");
await expect(card).toContainText(
"This machine is currently using another member's shared compute",
);
await expect(toggle).not.toBeChecked();
await expect(toggle).toBeDisabled();
// The switch is disabled, so a click can't fire onCheckedChange — but assert
// the destructive command never went out regardless.
const stopIssued = await page.evaluate(() =>
((window as E2eWindow).__BUZZ_E2E_COMMANDS__ ?? []).includes(
"mesh_stop_node",
),
);
expect(stopIssued).toBe(false);
});