feat(mesh): upgrade embedded mesh to v0.74 and harden shared compute (split 1/2 of #3467) (#3741)

## Summary

This is **part 1 of 2** split out from #3467 (per Tyler's request),
carrying only the mesh-scoped changes. The agent/ACP response-behavior
changes and the new `send_message` tool stay in #3467 as part 2. All
commits are @michaelneale's work, cherry-picked with authorship
preserved.

- Upgrade embedded Mesh to v0.74.0 (tag-pinned instead of commit rev)
and use canonical Gemma model IDs.
- Keep shared compute serving through member joins, roster changes, app
recovery, and community switching.
- Wait for actual model readiness and avoid resuming incomplete
downloads after quit.
- Leave `BUZZ_AGENT_THINKING_EFFORT` unset by default so each model's
chat template picks its own thinking default (`none` suppressed Gemma
tool-calling entirely; pinning `low` made Qwen3 burn ~4x output budget).
Explicit agent/persona/global values still win.

## Relationship to #3467

Contains the mesh commits from #3467 (`2cd640b23`, `0ad81c341`,
`ad13ed841`) rebased onto current main, with one deliberate exclusion:
the `crates/buzz-agent/src/llm.rs` reasoning→text parser change from
`2cd640b23` is **not** here. That change unconditionally affects every
OpenAI-compat/Responses provider, so it belongs with the reply-behavior
work in part 2, where it can be reviewed as what it is.

Not included (remaining in #3467 / part 2):
- typed `send_message` tool in dev-mcp + `BUZZ_ACP_SEND_MESSAGE_TOOL`
gating
- plain-reply delivery fallback in buzz-acp
(`BUZZ_ACP_DELIVER_PLAIN_REPLIES`)
- the mesh_agent_e2e P5/P6 rewrite (exists to prove the reply path)
- the two `env.insert` preset opt-ins in `relay_mesh.rs` for the flags
above
- the llm.rs parser change

This PR is independently mergeable; part 2's flags are all off by
default so it can land before or after.

## Testing

- `cargo test -p buzz-relay --locked` — 780 passed (one telemetry test
is order-sensitive under parallel default settings; passes in the
pre-push suite and standalone, unrelated to this diff — files untouched
here).
- `just desktop-tauri-test` (default features) — 1877 passed.
- `cargo test --locked --features mesh-llm` in `desktop/src-tauri` —
1961 passed, including the new relay-mesh preset and
coordinator/recovery tests.
- Both `Cargo.lock`s resolve with `--locked` against the v0.74.0 tag.
- Full pre-push hook suite green (rust-tests, desktop-check/test, tauri
checks).

Live validation of the mesh v0.74 upgrade itself is documented on #3467
(two-Mac cross-version test).

---------

Signed-off-by: Michael Neale <michael.neale@gmail.com>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: Michael Neale <michael.neale@gmail.com>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
This commit is contained in:
Tyler
2026-07-30 11:37:27 -04:00
committed by GitHub
parent 33bf7caa6e
commit 4933672eb4
17 changed files with 781 additions and 360 deletions

193
Cargo.lock generated
View File

@@ -43,6 +43,19 @@ dependencies = [
"subtle",
]
[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if 1.0.4",
"getrandom 0.3.4",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.4"
@@ -2177,7 +2190,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090"
dependencies = [
"data-encoding",
"syn 1.0.109",
"syn 2.0.117",
]
[[package]]
@@ -3009,8 +3022,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]]
@@ -3609,7 +3622,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.62.2",
"windows-core 0.61.2",
]
[[package]]
@@ -4501,8 +4514,8 @@ 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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"hex",
"mesh-llm-client",
@@ -4511,8 +4524,8 @@ 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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"mesh-llm-api-client",
@@ -4522,13 +4535,13 @@ 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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
[[package]]
name = "mesh-llm-client"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"async-trait",
@@ -4559,8 +4572,8 @@ 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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"dirs",
@@ -4575,8 +4588,8 @@ 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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"mesh-llm-host-runtime",
@@ -4585,8 +4598,8 @@ 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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"clap",
@@ -4597,8 +4610,8 @@ 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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"cc",
@@ -4610,8 +4623,8 @@ 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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"serde",
"serde_json",
@@ -4619,16 +4632,16 @@ 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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"mesh-llm-native-runtime",
]
[[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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"argon2",
@@ -4650,7 +4663,6 @@ dependencies = [
"http",
"http-body-util",
"httparse",
"if-addrs",
"iroh",
"json5",
"keyring",
@@ -4698,6 +4710,7 @@ dependencies = [
"serde_yaml",
"sha2 0.10.9",
"skippy-coordinator",
"skippy-ffi",
"skippy-protocol",
"skippy-runtime",
"skippy-server",
@@ -4720,8 +4733,8 @@ 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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"argon2",
"base64",
@@ -4742,8 +4755,8 @@ 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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"serde",
@@ -4753,8 +4766,8 @@ 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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"mesh-llm-types",
@@ -4767,8 +4780,8 @@ 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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"async-trait",
@@ -4784,8 +4797,8 @@ 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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"dirs",
@@ -4795,6 +4808,7 @@ dependencies = [
"reqwest 0.12.28",
"serde",
"serde_json",
"sha2 0.10.9",
"tar",
"tempfile",
"zip",
@@ -4802,8 +4816,8 @@ 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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"hex",
@@ -4815,16 +4829,16 @@ 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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"iroh",
]
[[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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"dirs",
@@ -4846,8 +4860,8 @@ 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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"mesh-llm-api-client",
@@ -4861,8 +4875,8 @@ 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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"dirs",
@@ -4872,8 +4886,8 @@ 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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"chrono",
@@ -4895,8 +4909,8 @@ 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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"hex",
"serde",
@@ -4906,13 +4920,13 @@ 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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
[[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"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"async-trait",
"mesh-llm-guardrails",
@@ -5038,8 +5052,8 @@ dependencies = [
[[package]]
name = "model-artifact"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"async-trait",
@@ -5049,8 +5063,8 @@ dependencies = [
[[package]]
name = "model-hf"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"async-trait",
@@ -5067,8 +5081,8 @@ dependencies = [
[[package]]
name = "model-package"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"bytes",
@@ -5087,16 +5101,16 @@ dependencies = [
[[package]]
name = "model-ref"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"serde",
]
[[package]]
name = "model-resolver"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"model-artifact",
@@ -5815,8 +5829,8 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "openai-frontend"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"async-trait",
"axum",
@@ -7368,9 +7382,9 @@ dependencies = [
[[package]]
name = "rmcp"
version = "1.7.0"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0810a9f717d9828f475fe1f629f4c305c8464b7f496c3a854b58d29e65f4058e"
checksum = "1d1f571c72940a19d9532fe52dbea8bc9912bf1d766c2970bb824056b86f3f59"
dependencies = [
"async-trait",
"base64",
@@ -7401,9 +7415,9 @@ dependencies = [
[[package]]
name = "rmcp-macros"
version = "1.7.0"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aefac48c364756e97f04c0401ba3231e8607882c7c1d92da0437dc16307904d"
checksum = "1aad0035b69380782d78ea95b508327e6deaa2235909053e596eea8f27b5e1d5"
dependencies = [
"darling 0.23.0",
"proc-macro2",
@@ -8153,8 +8167,8 @@ checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b"
[[package]]
name = "skippy-cache"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"blake3",
@@ -8163,29 +8177,29 @@ dependencies = [
[[package]]
name = "skippy-coordinator"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"thiserror 2.0.18",
]
[[package]]
name = "skippy-ffi"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"libloading",
]
[[package]]
name = "skippy-metrics"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
[[package]]
name = "skippy-protocol"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"prost",
"prost-build",
@@ -8195,8 +8209,8 @@ dependencies = [
[[package]]
name = "skippy-runtime"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"libc",
@@ -8209,9 +8223,10 @@ dependencies = [
[[package]]
name = "skippy-server"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"ahash",
"anyhow",
"async-trait",
"axum",
@@ -8237,8 +8252,8 @@ dependencies = [
[[package]]
name = "skippy-topology"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.73.1#43103c5c40292be688ac0261129bcbab0e7b9132"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"serde",
"serde_json",
@@ -10465,8 +10480,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]]

View File

@@ -84,8 +84,8 @@ async-compression = { version = "0.4.42", features = ["tokio", "gzip"] }
dev = ["buzz-auth/dev"]
[dev-dependencies]
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"] }
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"] }
mesh-llm-sdk = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.74.0", package = "mesh-llm-sdk", default-features = false, features = ["client", "serving"] }
mesh-llm-host-runtime = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.74.0", package = "mesh-llm-host-runtime", default-features = false, features = ["dynamic-native-runtime"] }
buzz-core = { workspace = true, features = ["test-utils"] }
buzz-auth = { workspace = true, features = ["dev"] }
reqwest = { workspace = true }

View File

@@ -278,7 +278,9 @@ async fn agent_chat_in_isolated_home(
.env("OPENAI_COMPAT_API_KEY", "buzz-mesh-local")
.env("OPENAI_COMPAT_API", "chat")
.env("BUZZ_AGENT_MAX_OUTPUT_TOKENS", "4096")
.env("BUZZ_AGENT_THINKING_EFFORT", "none")
// No BUZZ_AGENT_THINKING_EFFORT: apply_relay_mesh_env() deliberately
// leaves it unset so each model's chat template picks its own default.
// Pinning a value here would test a config the product does not ship.
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null());

View File

@@ -75,6 +75,19 @@ dependencies = [
"subtle",
]
[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if 1.0.4",
"getrandom 0.3.4",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.4"
@@ -1511,7 +1524,7 @@ version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -1748,7 +1761,7 @@ dependencies = [
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"windows 0.61.3",
"windows 0.62.2",
]
[[package]]
@@ -2152,7 +2165,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090"
dependencies = [
"data-encoding",
"syn 1.0.109",
"syn 2.0.118",
]
[[package]]
@@ -3102,8 +3115,8 @@ dependencies = [
"libc",
"log",
"rustversion",
"windows-link 0.1.3",
"windows-result 0.3.4",
"windows-link 0.2.1",
"windows-result 0.4.1",
]
[[package]]
@@ -3848,7 +3861,7 @@ dependencies = [
"tokio",
"tower-service",
"tracing",
"windows-registry 0.5.3",
"windows-registry 0.6.1",
]
[[package]]
@@ -3863,7 +3876,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.61.2",
"windows-core 0.62.2",
]
[[package]]
@@ -4939,8 +4952,8 @@ dependencies = [
[[package]]
name = "mesh-llm-api-client"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"hex",
"mesh-llm-client",
@@ -4949,8 +4962,8 @@ dependencies = [
[[package]]
name = "mesh-llm-api-server"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"mesh-llm-api-client",
@@ -4960,13 +4973,13 @@ dependencies = [
[[package]]
name = "mesh-llm-build-info"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
[[package]]
name = "mesh-llm-client"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"async-trait",
@@ -4997,8 +5010,8 @@ dependencies = [
[[package]]
name = "mesh-llm-config"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"dirs",
@@ -5013,8 +5026,8 @@ dependencies = [
[[package]]
name = "mesh-llm-embedded-runtime"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"mesh-llm-host-runtime",
@@ -5023,8 +5036,8 @@ dependencies = [
[[package]]
name = "mesh-llm-events"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"clap",
@@ -5035,8 +5048,8 @@ dependencies = [
[[package]]
name = "mesh-llm-gpu-bench"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"cc",
@@ -5048,8 +5061,8 @@ dependencies = [
[[package]]
name = "mesh-llm-guardrails"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"serde",
"serde_json",
@@ -5057,16 +5070,16 @@ dependencies = [
[[package]]
name = "mesh-llm-hardware-profile"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"mesh-llm-native-runtime",
]
[[package]]
name = "mesh-llm-host-runtime"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"argon2",
@@ -5088,7 +5101,6 @@ dependencies = [
"http",
"http-body-util",
"httparse",
"if-addrs",
"iroh",
"json5",
"keyring",
@@ -5136,6 +5148,7 @@ dependencies = [
"serde_yaml",
"sha2 0.10.9",
"skippy-coordinator",
"skippy-ffi",
"skippy-protocol",
"skippy-runtime",
"skippy-server",
@@ -5158,8 +5171,8 @@ dependencies = [
[[package]]
name = "mesh-llm-identity"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"argon2",
"base64 0.22.1",
@@ -5180,8 +5193,8 @@ dependencies = [
[[package]]
name = "mesh-llm-native-runtime"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"serde",
@@ -5191,8 +5204,8 @@ dependencies = [
[[package]]
name = "mesh-llm-node"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"mesh-llm-types",
@@ -5205,8 +5218,8 @@ dependencies = [
[[package]]
name = "mesh-llm-plugin"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"async-trait",
@@ -5222,8 +5235,8 @@ dependencies = [
[[package]]
name = "mesh-llm-plugin-manager"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"dirs",
@@ -5233,6 +5246,7 @@ dependencies = [
"reqwest 0.12.28",
"serde",
"serde_json",
"sha2 0.10.9",
"tar",
"tempfile",
"zip 2.4.2",
@@ -5240,8 +5254,8 @@ dependencies = [
[[package]]
name = "mesh-llm-protocol"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"hex",
@@ -5253,16 +5267,16 @@ dependencies = [
[[package]]
name = "mesh-llm-routing"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"iroh",
]
[[package]]
name = "mesh-llm-runtime-install"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"dirs",
@@ -5284,8 +5298,8 @@ dependencies = [
[[package]]
name = "mesh-llm-sdk"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"mesh-llm-api-client",
@@ -5299,8 +5313,8 @@ dependencies = [
[[package]]
name = "mesh-llm-skills"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"dirs",
@@ -5310,8 +5324,8 @@ dependencies = [
[[package]]
name = "mesh-llm-system"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"chrono",
@@ -5333,8 +5347,8 @@ dependencies = [
[[package]]
name = "mesh-llm-types"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"hex",
"serde",
@@ -5344,13 +5358,13 @@ dependencies = [
[[package]]
name = "mesh-llm-ui"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
[[package]]
name = "mesh-mixture-of-agents"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"async-trait",
"mesh-llm-guardrails",
@@ -5422,8 +5436,8 @@ dependencies = [
[[package]]
name = "model-artifact"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"async-trait",
@@ -5433,8 +5447,8 @@ dependencies = [
[[package]]
name = "model-hf"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"async-trait",
@@ -5451,8 +5465,8 @@ dependencies = [
[[package]]
name = "model-package"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"bytes",
@@ -5471,16 +5485,16 @@ dependencies = [
[[package]]
name = "model-ref"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"serde",
]
[[package]]
name = "model-resolver"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"model-artifact",
@@ -6134,7 +6148,7 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
dependencies = [
"proc-macro-crate 1.3.1",
"proc-macro-crate 2.0.2",
"proc-macro2",
"quote",
"syn 2.0.118",
@@ -6508,8 +6522,8 @@ dependencies = [
[[package]]
name = "openai-frontend"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"async-trait",
"axum",
@@ -6702,7 +6716,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
dependencies = [
"libc",
"windows-sys 0.45.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -7412,7 +7426,7 @@ version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042"
dependencies = [
"heck 0.4.1",
"heck 0.5.0",
"itertools",
"log",
"multimap",
@@ -9041,8 +9055,8 @@ checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
[[package]]
name = "skippy-cache"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"blake3",
@@ -9051,29 +9065,29 @@ dependencies = [
[[package]]
name = "skippy-coordinator"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"thiserror 2.0.18",
]
[[package]]
name = "skippy-ffi"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"libloading 0.8.9",
]
[[package]]
name = "skippy-metrics"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
[[package]]
name = "skippy-protocol"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"prost",
"prost-build",
@@ -9083,8 +9097,8 @@ dependencies = [
[[package]]
name = "skippy-runtime"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"anyhow",
"libc",
@@ -9097,9 +9111,10 @@ dependencies = [
[[package]]
name = "skippy-server"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"ahash",
"anyhow",
"async-trait",
"axum",
@@ -9125,8 +9140,8 @@ dependencies = [
[[package]]
name = "skippy-topology"
version = "0.73.1"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=f455d493a2ae82baf2a326e2d0fda351433b4b30#f455d493a2ae82baf2a326e2d0fda351433b4b30"
version = "0.74.0"
source = "git+https://github.com/Mesh-LLM/mesh-llm.git?tag=v0.74.0#e60b2fe43aa05271569fbeff2a457133aef456a1"
dependencies = [
"serde",
"serde_json",
@@ -10153,7 +10168,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom 0.3.4",
"getrandom 0.4.3",
"once_cell",
"rustix 1.1.4",
"windows-sys 0.61.2",
@@ -11789,7 +11804,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -12418,8 +12433,8 @@ dependencies = [
"log",
"serde",
"thiserror 2.0.18",
"windows 0.61.3",
"windows-core 0.61.2",
"windows 0.62.2",
"windows-core 0.62.2",
]
[[package]]

View File

@@ -96,14 +96,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", rev = "f455d493a2ae82baf2a326e2d0fda351433b4b30", 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 = "f455d493a2ae82baf2a326e2d0fda351433b4b30", 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", tag = "v0.74.0", 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.74.0", 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", rev = "f455d493a2ae82baf2a326e2d0fda351433b4b30", package = "mesh-llm-client", optional = true }
mesh-llm-node = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "f455d493a2ae82baf2a326e2d0fda351433b4b30", package = "mesh-llm-node", optional = true }
mesh-llm-system = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "f455d493a2ae82baf2a326e2d0fda351433b4b30", package = "mesh-llm-system", optional = true }
mesh-llm-events = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "f455d493a2ae82baf2a326e2d0fda351433b4b30", package = "mesh-llm-events", optional = true }
mesh-llm-client = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.74.0", package = "mesh-llm-client", optional = true }
mesh-llm-node = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.74.0", package = "mesh-llm-node", optional = true }
mesh-llm-system = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.74.0", package = "mesh-llm-system", optional = true }
mesh-llm-events = { git = "https://github.com/Mesh-LLM/mesh-llm.git", tag = "v0.74.0", package = "mesh-llm-events", optional = true }
base64 = "0.22"
sha2 = "0.11"
tar = "0.4"

View File

@@ -5,12 +5,35 @@ use tauri::{AppHandle, Manager, State};
use crate::{app_state::AppState, mesh_llm, relay};
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
struct MeshSharingConfig {
enabled: bool,
/// A fresh Share Compute request that must cross a process boundary before
/// it can start. Consumed before startup so an interrupted download is not
/// resumed on a later launch.
#[serde(default)]
start_on_next_launch: bool,
model_id: String,
max_vram_gb: Option<u64>,
/// Community relay where Share Compute was explicitly enabled. Older
/// configs predate community binding and restore against the active relay.
#[serde(default)]
relay_url: Option<String>,
}
fn pending_new_start_checkpoint(config: &MeshSharingConfig) -> MeshSharingConfig {
let mut checkpoint = config.clone();
checkpoint.enabled = false;
checkpoint.start_on_next_launch = false;
checkpoint
}
fn one_shot_restart_checkpoint(config: &MeshSharingConfig) -> MeshSharingConfig {
let mut checkpoint = config.clone();
checkpoint.enabled = false;
checkpoint.start_on_next_launch = true;
checkpoint
}
fn mesh_sharing_config_path(app: &AppHandle) -> Result<PathBuf, String> {
@@ -89,8 +112,10 @@ fn sharing_config_from_request(
.ok_or_else(|| "modelId is required for serve mode".to_string())?;
Ok(MeshSharingConfig {
enabled: true,
start_on_next_launch: false,
model_id: model_id.to_string(),
max_vram_gb: request.max_vram_gb,
relay_url: request.relay_url.clone(),
})
}
@@ -117,7 +142,7 @@ fn restart_to_share(
app: &AppHandle,
config: &MeshSharingConfig,
) -> CmdResult<mesh_llm::MeshNodeStatus> {
save_mesh_sharing_config(app, config)?;
save_mesh_sharing_config(app, &one_shot_restart_checkpoint(config))?;
let status = restarting_share_status(config);
app.request_restart();
Ok(status)
@@ -133,7 +158,7 @@ fn buzz_mesh_name_for_relay(relay_url: &str) -> String {
format!("buzz-community-{}", &digest[..32])
}
fn buzz_mesh_name(state: &AppState) -> String {
pub(super) fn buzz_mesh_name(state: &AppState) -> String {
buzz_mesh_name_for_relay(&relay::relay_ws_url_with_override(state))
}
@@ -150,8 +175,13 @@ fn advance_mesh_status_cursor(
Ok(cursor)
}
async fn query_mesh_discovery_events(state: &AppState) -> Result<Vec<nostr::Event>, String> {
let mut events = relay::query_relay(state, &[mesh_llm::relay_membership_filter()]).await?;
async fn query_mesh_discovery_events_at(
state: &AppState,
relay_url: &str,
) -> Result<Vec<nostr::Event>, String> {
let api_base_url = relay::relay_http_base_url(relay_url);
let mut events =
relay::query_relay_at(state, &api_base_url, &[mesh_llm::relay_membership_filter()]).await?;
let member_pubkeys = mesh_llm::current_member_pubkeys(&events);
if member_pubkeys.is_empty() {
// Distinguish "relay returned a membership snapshot listing zero
@@ -172,7 +202,7 @@ async fn query_mesh_discovery_events(state: &AppState) -> Result<Vec<nostr::Even
let mut previous_cursor: Option<(u64, String)> = None;
loop {
let page = relay::query_relay(state, &[status_filter.clone()]).await?;
let page = relay::query_relay_at(state, &api_base_url, &[status_filter.clone()]).await?;
let done = page.len() < mesh_llm::MESH_STATUS_PAGE_SIZE;
if !done {
let cursor = advance_mesh_status_cursor(&mut status_filter, &page)?;
@@ -188,6 +218,10 @@ async fn query_mesh_discovery_events(state: &AppState) -> Result<Vec<nostr::Even
}
}
async fn query_mesh_discovery_events(state: &AppState) -> Result<Vec<nostr::Event>, String> {
query_mesh_discovery_events_at(state, &relay::relay_ws_url_with_override(state)).await
}
/// Resolve the admission roster by intersecting member-signed mesh status
/// reporters with the current NIP-43 direct-member list.
///
@@ -201,6 +235,14 @@ pub(crate) async fn resolve_trusted_owner_ids(state: &AppState) -> Result<Vec<St
Ok(mesh_llm::owner_ids_from_events(&events))
}
pub(crate) async fn resolve_trusted_owner_ids_at(
state: &AppState,
relay_url: &str,
) -> Result<Vec<String>, String> {
let events = query_mesh_discovery_events_at(state, relay_url).await?;
Ok(mesh_llm::owner_ids_from_events(&events))
}
/// Resolve the roster for an initial node *start*, failing closed to self-only
/// (an empty roster) when the relay query fails. This is safe only at start:
/// there is no established allowlist to preserve yet. The periodic
@@ -244,10 +286,11 @@ fn buzz_mesh_join_targets(
/// Resolve the validated member endpoint this runtime should join to enter the
/// existing Buzz community mesh. `Ok(None)` means this machine is the first
/// live serving member (or is itself the shared bootstrap contact).
pub(crate) async fn resolve_buzz_mesh_join_targets(
pub(crate) async fn resolve_buzz_mesh_join_targets_at(
state: &AppState,
relay_url: &str,
) -> Result<Vec<mesh_llm::MeshServeTarget>, String> {
let events = query_mesh_discovery_events(state).await?;
let events = query_mesh_discovery_events_at(state, relay_url).await?;
let self_owner_id = mesh_llm::ensure_owner_identity()
.map_err(|error| format!("failed to load mesh owner identity: {error}"))?
.owner_id;
@@ -261,8 +304,11 @@ pub(crate) async fn resolve_buzz_mesh_join_targets(
/// snapshot. A node start used to repeat the full membership + status query
/// for each value, making Share Compute startup both slower and more exposed
/// to inconsistent snapshots.
async fn resolve_buzz_mesh_startup(state: &AppState) -> (Vec<String>, Option<String>) {
match query_mesh_discovery_events(state).await {
async fn resolve_buzz_mesh_startup_at(
state: &AppState,
relay_url: &str,
) -> (Vec<String>, Option<String>) {
match query_mesh_discovery_events_at(state, relay_url).await {
Ok(events) => {
let trusted_owner_ids = mesh_llm::owner_ids_from_events(&events);
let join_token = mesh_llm::ensure_owner_identity()
@@ -291,32 +337,60 @@ async fn resolve_buzz_mesh_startup(state: &AppState) -> (Vec<String>, Option<Str
}
pub(crate) async fn restore_mesh_sharing(app: &AppHandle, state: &AppState) -> CmdResult<()> {
let Some(config) = load_mesh_sharing_config(app)? else {
let Some(mut config) = load_mesh_sharing_config(app)? else {
return Ok(());
};
if !config.enabled || config.model_id.trim().is_empty() {
if (!config.enabled && !config.start_on_next_launch) || config.model_id.trim().is_empty() {
return Ok(());
}
config.model_id = mesh_llm::canonical_curated_model_id(&config.model_id).to_string();
if state.mesh_llm_runtime.lock().await.is_some() {
return Ok(());
}
let (trusted_owner_ids, join_token) = resolve_buzz_mesh_startup(state).await;
let relay_url = config
.relay_url
.clone()
.unwrap_or_else(|| relay::relay_ws_url_with_override(state));
let (trusted_owner_ids, join_token) = resolve_buzz_mesh_startup_at(state, &relay_url).await;
let mut runtime = state.mesh_llm_runtime.lock().await;
if runtime.is_some() {
return Ok(());
}
if config.start_on_next_launch {
// Consume a role-switch request before doing any potentially long model
// work. If Buzz exits during that work, the next launch stays stopped.
config = pending_new_start_checkpoint(&config);
save_mesh_sharing_config(app, &config)?;
}
// This is restoration of a previously inference-ready serving node. Keep
// the enabled checkpoint armed while restoring so a transient startup
// failure does not silently turn Share Compute off. New starts remain
// disarmed in `mesh_start_node` until their first inference probe passes.
let request = mesh_llm::StartMeshNodeRequest {
mode: mesh_llm::MeshNodeMode::Serve,
model_id: Some(config.model_id),
model_id: Some(config.model_id.clone()),
max_vram_gb: config.max_vram_gb,
join_token,
mesh_name: Some(buzz_mesh_name(state)),
mesh_name: Some(buzz_mesh_name_for_relay(&relay_url)),
relay_url: Some(relay_url),
trusted_owner_ids: Some(trusted_owner_ids),
};
let started = mesh_llm::DesktopMeshRuntime::start(request)
.await
.map_err(|error| format!("failed to restore Share Compute: {error:#}"))?;
if let Err(error) = wait_for_mesh_inference(&config.model_id).await {
let cleanup = started.stop().await;
if let Err(cleanup_error) = cleanup {
eprintln!(
"buzz-mesh: restored node failed inference readiness and cleanup was incomplete: {cleanup_error:#}"
);
}
return Err(format!("failed to restore Share Compute: {error}"));
}
*runtime = Some(started);
config.enabled = true;
config.start_on_next_launch = false;
save_mesh_sharing_config(app, &config)?;
drop(runtime);
mesh_llm::publish_current_status_once(app, "restore").await;
Ok(())
@@ -328,6 +402,11 @@ pub async fn mesh_start_node(
state: State<'_, AppState>,
mut request: mesh_llm::StartMeshNodeRequest,
) -> CmdResult<mesh_llm::MeshNodeStatus> {
let relay_url = relay::relay_ws_url_with_override(&state);
request.relay_url = Some(relay_url.clone());
if let Some(model_id) = request.model_id.as_mut() {
*model_id = mesh_llm::canonical_curated_model_id(model_id).to_string();
}
let sharing_config = if request.mode == mesh_llm::MeshNodeMode::Serve {
Some(sharing_config_from_request(&request)?)
} else {
@@ -362,13 +441,14 @@ pub async fn mesh_start_node(
// Frontend requests never carry a roster. Resolve it and the bootstrap
// endpoint from one snapshot so UI startup does not repeat relay probes.
if request.trusted_owner_ids.is_none() || request.join_token.is_none() {
let (trusted_owner_ids, join_token) = resolve_buzz_mesh_startup(&state).await;
let (trusted_owner_ids, join_token) =
resolve_buzz_mesh_startup_at(&state, &relay_url).await;
request.trusted_owner_ids.get_or_insert(trusted_owner_ids);
if request.join_token.is_none() {
request.join_token = join_token;
}
}
request.mesh_name = Some(buzz_mesh_name(&state));
request.mesh_name = Some(buzz_mesh_name_for_relay(&relay_url));
let mut runtime = state.mesh_llm_runtime.lock().await;
let plan = match runtime.as_ref() {
@@ -386,6 +466,13 @@ pub async fn mesh_start_node(
return Err("mesh node is already running".to_string());
}
if let Some(config) = sharing_config.as_ref() {
// Do not arm launch restoration until the exact inference path used by
// agents succeeds. Mesh may bind its ports after primary weights load
// while package layers are still downloading.
save_mesh_sharing_config(&app, &pending_new_start_checkpoint(config))?;
}
let started = mesh_llm::DesktopMeshRuntime::start(request)
.await
.map_err(|error| format!("{error:#}"))?;
@@ -409,6 +496,21 @@ pub async fn mesh_start_node(
));
}
};
if let Some(config) = sharing_config.as_ref() {
if let Err(error) = wait_for_mesh_inference(&config.model_id).await {
let cleanup = started.stop().await;
if let Err(cleanup_error) = &cleanup {
eprintln!(
"buzz-mesh: started node failed inference readiness and cleanup was incomplete: {cleanup_error:#}"
);
}
drop(runtime);
app.request_restart();
return Err(format!(
"mesh node started but inference never became ready: {error}; Buzz is restarting to guarantee cleanup"
));
}
}
*runtime = Some(started);
drop(runtime);
if let Some(config) = sharing_config.as_ref() {
@@ -612,6 +714,7 @@ pub(crate) async fn ensure_client_node_for_model(
max_vram_gb: None,
join_token: Some(join_token.clone()),
mesh_name: Some(buzz_mesh_name(state)),
relay_url: Some(relay::relay_ws_url_with_override(state)),
trusted_owner_ids: Some(resolve_trusted_owner_ids_or_self_only(state).await),
};
let mut runtime = state.mesh_llm_runtime.lock().await;
@@ -753,6 +856,18 @@ pub(crate) async fn ensure_relay_mesh_for_record(
}
}
}
// A persisted Share Compute configuration is authoritative about this
// machine's role. If no runtime is currently tracked (for example after a
// clean process restart), restore the serving node instead of treating an
// agent request as permission to replace it with a client node.
if load_mesh_sharing_config(app)?
.is_some_and(|config| config.enabled && !config.model_id.trim().is_empty())
{
restore_mesh_sharing(app, &state).await?;
return wait_for_mesh_inference(model_id).await;
}
let target = match resolve_mesh_bootstrap_target(&state, model_id).await {
Ok(Some(target)) => target,
Ok(None) => {
@@ -768,15 +883,9 @@ pub(crate) async fn ensure_relay_mesh_for_record(
}
};
// Serve→Client re-arm transition (micspiral review #3, intentional-by-design):
// if the dead ingress belonged to a *serve* node with running consumer
// agents, this re-arms it as a Client (`MeshNodeMode::Client`). That is the
// correct/safe recovery here — config-backed serve restoration is
// `restore_mesh_sharing`'s job (`MeshNodeMode::Serve`), and
// `ensure_client_node_for_model` reuses any live runtime of *either* mode
// (the router resolves per-request), so it only cold-starts a Client when
// there is genuinely no runtime. Falling back to Client if a serve node
// crashed under local pressure is a desirable fail-safe, not a regression.
// No serving configuration exists, so this is a genuine consumer-only
// start. A configured serving machine is restored above and never reaches
// this client fallback.
ensure_client_node_for_model(&state, model_id, Some(target.endpoint_addr)).await?;
wait_for_mesh_inference(model_id).await
}
@@ -792,14 +901,17 @@ pub async fn mesh_stop_node(
// 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 (taken, bound_relay_url) = {
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()
let bound_relay_url = guard
.as_ref()
.and_then(|runtime| runtime.start_request().relay_url.clone());
(guard.take(), bound_relay_url)
};
if let Some(runtime) = taken {
runtime.stop().await.map_err(|error| error.to_string())?;
@@ -808,11 +920,13 @@ pub async fn mesh_stop_node(
&app,
&MeshSharingConfig {
enabled: false,
start_on_next_launch: false,
model_id: String::new(),
max_vram_gb: None,
relay_url: None,
},
)?;
mesh_llm::publish_stopped_status_once(&app, "stop").await;
mesh_llm::publish_stopped_status_once_at(&app, bound_relay_url.as_deref(), "stop").await;
Ok(mesh_llm::stopped_status())
}

View File

@@ -110,6 +110,74 @@ fn buzz_mesh_name_is_stable_and_does_not_expose_the_relay() {
assert!(!first.contains("example"));
}
#[test]
fn sharing_config_keeps_the_community_where_sharing_was_enabled() {
let request = mesh_llm::StartMeshNodeRequest {
mode: mesh_llm::MeshNodeMode::Serve,
model_id: Some("test-model".to_string()),
max_vram_gb: Some(24),
join_token: None,
mesh_name: Some("buzz-community-test".to_string()),
relay_url: Some("wss://community.example".to_string()),
trusted_owner_ids: Some(Vec::new()),
};
let config = sharing_config_from_request(&request).expect("valid sharing config");
assert_eq!(config.relay_url.as_deref(), Some("wss://community.example"));
}
#[test]
fn legacy_sharing_config_without_community_binding_still_loads() {
let config: MeshSharingConfig = serde_json::from_value(serde_json::json!({
"enabled": true,
"modelId": "test-model",
"maxVramGb": null
}))
.expect("legacy sharing config");
assert_eq!(config.relay_url, None);
assert!(!config.start_on_next_launch);
}
#[test]
fn new_start_checkpoint_prevents_incomplete_download_restore() {
let config = MeshSharingConfig {
enabled: true,
start_on_next_launch: false,
model_id: "test-model".to_string(),
max_vram_gb: Some(24),
relay_url: Some("wss://community.example".to_string()),
};
let checkpoint = pending_new_start_checkpoint(&config);
assert!(!checkpoint.enabled);
assert!(!checkpoint.start_on_next_launch);
assert_eq!(checkpoint.model_id, config.model_id);
assert_eq!(checkpoint.max_vram_gb, config.max_vram_gb);
assert_eq!(checkpoint.relay_url, config.relay_url);
}
#[test]
fn role_switch_checkpoint_starts_exactly_once_after_restart() {
let config = MeshSharingConfig {
enabled: true,
start_on_next_launch: false,
model_id: "test-model".to_string(),
max_vram_gb: Some(24),
relay_url: Some("wss://community.example".to_string()),
};
let restart = one_shot_restart_checkpoint(&config);
assert!(!restart.enabled);
assert!(restart.start_on_next_launch);
let consumed = pending_new_start_checkpoint(&restart);
assert!(!consumed.enabled);
assert!(!consumed.start_on_next_launch);
assert_eq!(consumed.model_id, config.model_id);
assert_eq!(consumed.relay_url, config.relay_url);
}
#[test]
fn readiness_failure_is_catalog_sync_when_model_never_visible() {
assert_eq!(
@@ -345,6 +413,7 @@ fn ensure_serve_runtime_serves_other_model() {
max_vram_gb: None,
join_token: None,
mesh_name: None,
relay_url: None,
trusted_owner_ids: None,
})
.await

View File

@@ -42,15 +42,51 @@ pub fn apply_relay_mesh_env(
RELAY_MESH_PREFER_MESH_FOR_AUTO_ENV.to_string(),
"1".to_string(),
);
// Keep the requested response inside smaller local-model context windows,
// and spend that budget on an answer/tool call instead of hidden reasoning.
// Without both settings Qwen3 either fails the router's fit check at the
// agent default (32K) or can consume a tight cap before serializing a tool.
env.insert(
"BUZZ_AGENT_MAX_OUTPUT_TOKENS".to_string(),
"4096".to_string(),
);
env.insert("BUZZ_AGENT_THINKING_EFFORT".to_string(), "none".to_string());
// Keep the requested response inside smaller local-model context windows.
// These are defaults, not policy: the effective agent/persona/global env
// may deliberately choose a smaller cap or a different effort. This function
// runs after those layers during readiness, so never clobber their values.
insert_default_if_unset(env, "BUZZ_AGENT_MAX_OUTPUT_TOKENS", "4096");
// Deliberately no BUZZ_AGENT_THINKING_EFFORT default: mesh translates
// `reasoning_effort` into the chat template's `enable_thinking` flag, so any
// value we pick overrides each model's own template default — and the right
// value is model-specific. Measured with the real prompt and toolset:
// gemma-4-E4B delivers 0/8 at `none` but 6/6 with the field absent, while
// Qwen3-8B delivers 8/8 either way and burns ~4x the output tokens once
// thinking is on (121 -> ~470), risking the 4096 cap. Omitting the field
// lets every model use its own default; explicit agent/persona/global
// values still apply.
}
#[cfg(feature = "mesh-llm")]
fn insert_default_if_unset(
env: &mut std::collections::BTreeMap<String, String>,
key: &str,
value: &str,
) {
if env.get(key).is_none_or(|current| current.trim().is_empty()) {
env.insert(key.to_string(), value.to_string());
}
}
/// Build the final Mesh-specific process overrides from the already-resolved
/// harness environment. Only user-owned generation controls are seeded: the
/// derived provider/base URL/model values remain authoritative, and unrelated
/// credentials (notably `OPENAI_API_KEY`) must not be copied back after the
/// spawn path removes them.
#[cfg(feature = "mesh-llm")]
pub fn relay_mesh_process_env(
effective_env: &std::collections::BTreeMap<String, String>,
model: &str,
) -> std::collections::BTreeMap<String, String> {
let mut env = std::collections::BTreeMap::new();
for key in ["BUZZ_AGENT_MAX_OUTPUT_TOKENS", "BUZZ_AGENT_THINKING_EFFORT"] {
if let Some(value) = effective_env.get(key) {
env.insert(key.to_string(), value.clone());
}
}
apply_relay_mesh_env(&mut env, Some(RELAY_MESH_PROVIDER_ID), Some(model));
env
}
#[cfg(all(test, feature = "mesh-llm"))]
@@ -60,7 +96,7 @@ mod tests {
use super::*;
#[test]
fn native_provider_uses_context_safe_non_reasoning_budget() {
fn native_provider_uses_context_safe_tool_calling_budget() {
let mut env = BTreeMap::new();
apply_relay_mesh_env(
&mut env,
@@ -72,14 +108,63 @@ mod tests {
env.get("BUZZ_AGENT_MAX_OUTPUT_TOKENS").map(String::as_str),
Some("4096")
);
assert_eq!(
env.get("BUZZ_AGENT_THINKING_EFFORT").map(String::as_str),
Some("none")
);
// Must stay unset: any value we pick overrides the model's own chat
// template default, and the right value is model-specific ("none"
// stops gemma tool-calling; enabling thinking makes Qwen3 burn ~4x the
// output budget).
assert_eq!(env.get("BUZZ_AGENT_THINKING_EFFORT"), None);
assert_eq!(
env.get(RELAY_MESH_PREFER_MESH_FOR_AUTO_ENV)
.map(String::as_str),
Some("1")
);
}
#[test]
fn native_provider_preserves_explicit_generation_controls() {
let mut env = BTreeMap::from([
(
"BUZZ_AGENT_MAX_OUTPUT_TOKENS".to_string(),
"2048".to_string(),
),
("BUZZ_AGENT_THINKING_EFFORT".to_string(), "high".to_string()),
]);
apply_relay_mesh_env(
&mut env,
Some(RELAY_MESH_PROVIDER_ID),
Some(RELAY_MESH_AUTO_MODEL_ID),
);
assert_eq!(
env.get("BUZZ_AGENT_MAX_OUTPUT_TOKENS").map(String::as_str),
Some("2048")
);
assert_eq!(
env.get("BUZZ_AGENT_THINKING_EFFORT").map(String::as_str),
Some("high")
);
}
#[test]
fn process_env_seeds_controls_without_restoring_unrelated_credentials() {
let effective_env = BTreeMap::from([
(
"BUZZ_AGENT_MAX_OUTPUT_TOKENS".to_string(),
"1024".to_string(),
),
("OPENAI_API_KEY".to_string(), "must-not-leak".to_string()),
]);
let env = relay_mesh_process_env(&effective_env, "Gemma-4");
assert_eq!(
env.get("BUZZ_AGENT_MAX_OUTPUT_TOKENS").map(String::as_str),
Some("1024")
);
assert_eq!(
env.get("OPENAI_COMPAT_MODEL").map(String::as_str),
Some("Gemma-4")
);
assert!(!env.contains_key("OPENAI_API_KEY"));
}
}

View File

@@ -869,12 +869,7 @@ pub fn spawn_agent_child(
// uses the same trim semantics as the preflight callers.
#[cfg(feature = "mesh-llm")]
if let Some(ref mesh_model_id) = mesh_model_id {
let mut mesh_env = std::collections::BTreeMap::new();
super::apply_relay_mesh_env(
&mut mesh_env,
Some(super::RELAY_MESH_PROVIDER_ID),
Some(mesh_model_id.as_str()),
);
let mesh_env = super::relay_mesh_process_env(&descriptor.env, mesh_model_id);
command.env_remove("OPENAI_API_KEY");
for (key, value) in mesh_env {
command.env(key, value);

View File

@@ -19,12 +19,14 @@ use mesh_llm_system::vram::{format_rated_capacity, rated_capacity_gb};
/// The large pick is resolved through mesh-llm's remote catalog
/// (huggingface.co/datasets/meshllm/catalog), so it does not need to exist in
/// the compiled `MODEL_CATALOG`; the entry is synthesized below.
const CURATED_LARGE: &str = "gemma-4-26B-A4B-it-UD-Q4_K_M";
const CURATED_LARGE: &str = "unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q4_K_M";
const CURATED_LARGE_ALIAS: &str = "gemma-4-26B-A4B-it-UD-Q4_K_M";
const CURATED_LARGE_SIZE: &str = "17GB";
const CURATED_LARGE_FILE: &str = "gemma-4-26B-A4B-it-UD-Q4_K_M.gguf";
const CURATED_LARGE_DESCRIPTION: &str =
"Gemma 4 26B MoE (4B active) — Buzz default for 64GB+ machines";
const CURATED_SMALL: &str = "Gemma-4-E4B-it-Q4_K_M";
const CURATED_SMALL: &str = "unsloth/gemma-4-E4B-it-GGUF:Q4_K_M";
const CURATED_SMALL_ALIAS: &str = "Gemma-4-E4B-it-Q4_K_M";
/// Rated-capacity boundary between the two curated tiers, in GB (marketing
/// capacity — a "64GB" Mac rates as 64 even though usable AI memory is less).
const CURATED_LARGE_MIN_RATED_GB: u64 = 64;
@@ -37,6 +39,16 @@ fn buzz_recommended_model(rated_gb: Option<u64>) -> &'static str {
}
}
/// Convert Buzz's pre-0.74 curated package aliases into the canonical model
/// ids advertised and accepted by Mesh's OpenAI ingress.
pub(crate) fn canonical_curated_model_id(model_id: &str) -> &str {
match model_id.trim() {
CURATED_SMALL_ALIAS => CURATED_SMALL,
CURATED_LARGE_ALIAS => CURATED_LARGE,
other => other,
}
}
/// How a model sits inside this machine's usable AI memory.
/// Mirrors mesh-llm's private `fit_code_for_size_label` thresholds.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
@@ -146,12 +158,13 @@ fn build_catalog(
.filter(|m| !is_draft_only(&m.name))
.map(|m| {
let size_gb = parse_size_gb(&m.size);
let name = canonical_curated_model_id(&m.name).to_string();
MeshCatalogEntry {
fit: fit_code(size_gb, vram_gb),
installed: is_installed(&m.file, &m.name),
installed: is_installed(&m.file, &name) || is_installed(&m.file, &m.name),
recommended: false,
curated: false,
name: m.name.clone(),
name,
size: m.size.clone(),
size_gb,
description: m.description.clone(),
@@ -166,7 +179,8 @@ fn build_catalog(
let size_gb = parse_size_gb(CURATED_LARGE_SIZE);
entries.push(MeshCatalogEntry {
fit: fit_code(size_gb, vram_gb),
installed: is_installed(CURATED_LARGE_FILE, CURATED_LARGE),
installed: is_installed(CURATED_LARGE_FILE, CURATED_LARGE)
|| is_installed(CURATED_LARGE_FILE, CURATED_LARGE_ALIAS),
recommended: false,
curated: false,
name: CURATED_LARGE.to_string(),
@@ -256,6 +270,8 @@ mod tests {
#[test]
fn recommendation_follows_buzz_curated_tiers() {
assert_eq!(CURATED_SMALL, "unsloth/gemma-4-E4B-it-GGUF:Q4_K_M");
assert_eq!(CURATED_LARGE, "unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q4_K_M");
// 64GB+ rated machines get the large curated pick.
let large = build_catalog(None, 64_000_000_000, 64.0, &[]);
assert_eq!(large.recommended.as_deref(), Some(CURATED_LARGE));
@@ -269,6 +285,22 @@ mod tests {
assert_eq!(tiny.recommended.as_deref(), Some(CURATED_SMALL));
}
#[test]
fn curated_package_aliases_migrate_to_openai_model_ids() {
assert_eq!(
canonical_curated_model_id(CURATED_SMALL_ALIAS),
CURATED_SMALL
);
assert_eq!(
canonical_curated_model_id(CURATED_LARGE_ALIAS),
CURATED_LARGE
);
assert_eq!(
canonical_curated_model_id("other/model:Q4"),
"other/model:Q4"
);
}
#[test]
fn curated_picks_lead_the_catalog() {
let catalog = build_catalog(None, 96_000_000_000, 96.0, &[]);

View File

@@ -132,7 +132,7 @@ pub async fn start_coordinator(app: AppHandle) {
/// MeshLLM establishes the encrypted peer transport itself.
async fn reconcile_buzz_mesh_join(app: &AppHandle) -> Result<(), String> {
let state = app.state::<AppState>();
let peer_ids = {
let (peer_ids, relay_url) = {
let runtime = state.mesh_llm_runtime.lock().await;
let Some(runtime) = runtime.as_ref() else {
return Ok(());
@@ -141,10 +141,16 @@ async fn reconcile_buzz_mesh_join(app: &AppHandle) -> Result<(), String> {
.status_report_payload()
.await
.map_err(|error| error.to_string())?;
visible_peer_ids(&payload)
let relay_url = runtime
.start_request()
.relay_url
.clone()
.unwrap_or_else(|| crate::relay::relay_ws_url_with_override(&state));
(visible_peer_ids(&payload), relay_url)
};
let targets = crate::commands::mesh_llm::resolve_buzz_mesh_join_targets(&state).await?;
let targets =
crate::commands::mesh_llm::resolve_buzz_mesh_join_targets_at(&state, &relay_url).await?;
let Some(target) = targets
.into_iter()
.find(|target| !target_is_visible(target, &peer_ids))
@@ -201,8 +207,13 @@ fn target_is_visible(target: &crate::mesh_llm::MeshServeTarget, peer_ids: &[Stri
enum RosterReconcileAction {
/// Keep the running allowlist untouched (no-op, or a failure we ride out).
Keep,
/// Restart the node with a freshly resolved roster.
Restart(Vec<String>),
/// Restart Buzz so MeshLLM is rebuilt with a freshly resolved roster.
///
/// MeshLLM's native listeners are process-owned in practice: stopping and
/// starting the embedded runtime in one process can terminate Buzz or race
/// ports 9337/3131. The process boundary is therefore part of the safety
/// contract, not an implementation detail.
RestartProcess,
/// Observed a *shrink* (or empty) once. Hold the current allowlist and
/// require the same reduced roster on the next poll before tearing down,
/// so a single transient short-read never drops a member mid-inference.
@@ -224,9 +235,9 @@ fn roster_shrinks(current: &[String], fresh: &[String]) -> bool {
/// Rules:
/// - query failed (`Err`) → `Keep` (never de-admit on a relay blip)
/// - resolved roster == current → `Keep` (no-op)
/// - grows (only additions) → `Restart` immediately (fast admission)
/// - grows (only additions) → `RestartProcess` immediately (fast admission)
/// - shrinks/empties, first observation → `AwaitConfirm` (hold, re-check next poll)
/// - shrinks/empties, confirmed → `Restart` (same reduced roster twice)
/// - shrinks/empties, confirmed → `RestartProcess` (same reduced roster twice)
fn roster_reconcile_action(
current_owners: &[String],
pending_shrink: Option<&[String]>,
@@ -248,13 +259,13 @@ fn roster_reconcile_action(
// Growth (pure additions) is safe to apply immediately.
if !roster_shrinks(current_owners, &fresh) {
return RosterReconcileAction::Restart(fresh);
return RosterReconcileAction::RestartProcess;
}
// A shrink (including down to empty) must be confirmed across two
// consecutive polls with the *same* reduced roster before we tear down.
match pending_shrink {
Some(pending) if pending == fresh => RosterReconcileAction::Restart(fresh),
Some(pending) if pending == fresh => RosterReconcileAction::RestartProcess,
_ => RosterReconcileAction::AwaitConfirm(fresh),
}
}
@@ -283,8 +294,13 @@ async fn reconcile_roster(
// other member on a transient relay blip (the flapping restart loop). Keep
// the current allowlist and try again on the next poll. A shrink is held
// for one extra poll (hysteresis) so a single short-read never tears down.
let query = crate::commands::mesh_llm::resolve_trusted_owner_ids(&state).await;
let fresh = match roster_reconcile_action(current_owners, pending_shrink.as_deref(), query) {
let relay_url = current_request
.relay_url
.as_deref()
.map(str::to_owned)
.unwrap_or_else(|| crate::relay::relay_ws_url_with_override(&state));
let query = crate::commands::mesh_llm::resolve_trusted_owner_ids_at(&state, &relay_url).await;
match roster_reconcile_action(current_owners, pending_shrink.as_deref(), query) {
RosterReconcileAction::Keep => {
*pending_shrink = None;
return Ok(());
@@ -294,34 +310,12 @@ async fn reconcile_roster(
*pending_shrink = Some(reduced);
return Ok(());
}
RosterReconcileAction::Restart(fresh) => {
RosterReconcileAction::RestartProcess => {
*pending_shrink = None;
fresh
}
};
}
let mut request = current_request.clone();
request.trusted_owner_ids = Some(fresh);
// Bootstrap endpoints are live device state, not configuration. The
// endpoint used at the previous start may belong to the member that just
// left or to a device whose iroh identity rotated while offline. Resolve a
// fresh validated peer for this restart; starting isolated is safe because
// the join watcher will converge it when a member next publishes.
request.join_token = match crate::commands::mesh_llm::resolve_buzz_mesh_join_targets(&state)
.await
{
Ok(targets) => targets
.into_iter()
.next()
.map(|target| target.endpoint_addr),
Err(error) => {
eprintln!(
"buzz-mesh: could not refresh bootstrap endpoint for roster restart; starting isolated: {error}"
);
None
}
};
let mut guard = state.mesh_llm_runtime.lock().await;
let guard = state.mesh_llm_runtime.lock().await;
let startup_pending = match guard.as_ref() {
Some(runtime) => runtime.is_starting().await,
None => false,
@@ -342,24 +336,14 @@ async fn reconcile_roster(
// snapshot.
return Ok(());
}
let Some(running) = guard.take() else {
if guard.is_none() {
return Ok(());
};
eprintln!("buzz-mesh: membership roster changed; restarting mesh node with fresh allowlist");
if let Err(error) = running.stop().await {
drop(guard);
eprintln!(
"buzz-mesh: stopping mesh node for roster restart failed; restarting Buzz instead of racing the occupied ingress: {error}"
);
app.request_restart();
return Err(format!(
"mesh node shutdown failed during roster change: {error}"
));
}
let replacement = crate::mesh_llm::DesktopMeshRuntime::start(request)
.await
.map_err(|error| format!("mesh node restart after roster change failed: {error:#}"))?;
*guard = Some(replacement);
drop(guard);
eprintln!(
"buzz-mesh: membership roster changed; restarting Buzz to rebuild MeshLLM with the fresh community allowlist"
);
app.request_restart();
Ok(())
}
@@ -377,11 +361,15 @@ pub(crate) async fn publish_current_status_once(app: &AppHandle, reason: &str) {
}
}
pub(crate) async fn publish_stopped_status_once(app: &AppHandle, reason: &str) {
pub(crate) async fn publish_stopped_status_once_at(
app: &AppHandle,
relay_url: Option<&str>,
reason: &str,
) {
let state = app.state::<AppState>();
match tokio::time::timeout(
STATUS_PUBLISH_TIMEOUT,
publish_stopped_status_for_state(&state),
publish_stopped_status_for_state(&state, relay_url),
)
.await
{
@@ -396,26 +384,43 @@ pub(crate) async fn publish_stopped_status_once(app: &AppHandle, reason: &str) {
async fn publish_current_status_for_state(state: &AppState) -> Result<(), String> {
let identity = super::ensure_owner_identity()
.map_err(|error| format!("failed to load mesh owner identity: {error}"))?;
let mut payload = {
let (mut payload, relay_url) = {
let runtime = state.mesh_llm_runtime.lock().await;
match runtime.as_ref() {
Some(runtime) => runtime
.status_report_payload()
.await
.map_err(|error| error.to_string())?,
None => stopped_status_payload(&identity),
Some(runtime) => {
let payload = runtime
.status_report_payload()
.await
.map_err(|error| error.to_string())?;
let relay_url = runtime
.start_request()
.relay_url
.clone()
.unwrap_or_else(|| crate::relay::relay_ws_url_with_override(state));
(payload, relay_url)
}
None => (
stopped_status_payload(&identity),
crate::relay::relay_ws_url_with_override(state),
),
}
};
bind_payload_to_member(state, &identity, &mut payload)?;
publish_status_report(state, payload).await
publish_status_report_at(state, &relay_url, payload).await
}
async fn publish_stopped_status_for_state(state: &AppState) -> Result<(), String> {
async fn publish_stopped_status_for_state(
state: &AppState,
relay_url: Option<&str>,
) -> Result<(), String> {
let identity = super::ensure_owner_identity()
.map_err(|error| format!("failed to load mesh owner identity: {error}"))?;
let mut payload = stopped_status_payload(&identity);
bind_payload_to_member(state, &identity, &mut payload)?;
publish_status_report(state, payload).await
let relay_url = relay_url
.map(str::to_owned)
.unwrap_or_else(|| crate::relay::relay_ws_url_with_override(state));
publish_status_report_at(state, &relay_url, payload).await
}
fn stopped_status_payload(identity: &super::identity::OwnerIdentity) -> serde_json::Value {
@@ -469,13 +474,21 @@ pub(crate) fn build_status_report_event(
.tags([d, k]))
}
pub(crate) async fn publish_status_report(
async fn publish_status_report_at(
state: &AppState,
relay_url: &str,
payload: serde_json::Value,
) -> Result<(), String> {
crate::relay::submit_event(build_status_report_event(payload)?, state)
.await
.map(|_| ())
let api_base_url = crate::relay::relay_http_base_url(relay_url);
let keys = state.signing_keys()?;
crate::relay::submit_event_at_with_keys(
build_status_report_event(payload)?,
state,
&api_base_url,
&keys,
)
.await
.map(|_| ())
}
#[cfg(test)]
@@ -554,11 +567,11 @@ mod tests {
// Growth (pure additions) applies immediately — fast admission is fine.
#[test]
fn roster_growth_restarts_immediately() {
fn roster_growth_requests_process_restart_immediately() {
let current = vec!["owner-a".to_string()];
let fresh = vec!["owner-a".to_string(), "owner-c".to_string()];
let action = roster_reconcile_action(&current, None, Ok(fresh.clone()));
assert_eq!(action, RosterReconcileAction::Restart(fresh));
let action = roster_reconcile_action(&current, None, Ok(fresh));
assert_eq!(action, RosterReconcileAction::RestartProcess);
}
// A shrink is NOT applied on first observation — it must be confirmed.
@@ -572,11 +585,11 @@ mod tests {
// The same reduced roster on two consecutive polls confirms the shrink.
#[test]
fn roster_shrink_restarts_once_confirmed() {
fn roster_shrink_requests_process_restart_once_confirmed() {
let current = vec!["owner-a".to_string(), "owner-b".to_string()];
let reduced = vec!["owner-a".to_string()];
let action = roster_reconcile_action(&current, Some(&reduced), Ok(reduced.clone()));
assert_eq!(action, RosterReconcileAction::Restart(reduced));
assert_eq!(action, RosterReconcileAction::RestartProcess);
}
// A shrink that changes between polls is not confirmed — it re-holds with
@@ -600,7 +613,7 @@ mod tests {
assert_eq!(first, RosterReconcileAction::AwaitConfirm(Vec::new()));
let empty: Vec<String> = Vec::new();
let confirmed = roster_reconcile_action(&current, Some(&empty), Ok(Vec::new()));
assert_eq!(confirmed, RosterReconcileAction::Restart(Vec::new()));
assert_eq!(confirmed, RosterReconcileAction::RestartProcess);
}
// A shrink followed by recovery to the full roster cancels the teardown.

View File

@@ -1,7 +1,7 @@
use std::collections::BTreeMap;
mod coordinator;
pub(crate) use coordinator::{publish_current_status_once, publish_stopped_status_once};
pub(crate) use coordinator::{publish_current_status_once, publish_stopped_status_once_at};
pub use coordinator::{start_coordinator, MeshCoordinator, KIND_BUZZ_MESH_MEMBER_STATUS};
mod discovery;
@@ -14,6 +14,7 @@ pub(crate) use discovery::{
use discovery::{device_name_from_status, endpoint_id_from_status, enrich_status_payload_identity};
mod catalog;
pub(crate) use catalog::canonical_curated_model_id;
pub use catalog::{model_catalog, MeshModelCatalog};
mod identity;
@@ -200,6 +201,11 @@ pub struct StartMeshNodeRequest {
/// accepted from the frontend and contains no relay address.
#[serde(default, skip_deserializing)]
pub mesh_name: Option<String>,
/// Relay this runtime's community membership and discovery are bound to.
/// Injected by the backend when sharing starts and retained across UI
/// workspace switches; moving a share requires an explicit stop/start.
#[serde(default, skip_deserializing)]
pub relay_url: Option<String>,
/// Mesh owner ids admitted to this node (the member roster from
/// member-signed discovery notes). `None` = caller did not resolve a roster
/// (tests, direct invocations): the node runs without allowlist
@@ -308,17 +314,20 @@ pub const MESH_WORKER_STACK_SIZE: usize = 8 * 1024 * 1024;
/// before the node starts. Without this the download happens *inside*
/// `serve::start()` where the UI can only show a frozen "starting…" state.
/// Already-installed models return immediately from the cache scan.
async fn ensure_model_downloaded(model: &str) -> anyhow::Result<()> {
let model_owned = model.to_string();
let installed = tokio::task::spawn_blocking(move || {
async fn model_is_installed(model: &str) -> bool {
let model_owned = model.replace("@main", "");
tokio::task::spawn_blocking(move || {
let cache = mesh_llm_node::models::default_huggingface_cache_dir();
mesh_llm_node::models::scan_installed_models(cache)
.iter()
.any(|m| m.model_ref.contains(&model_owned))
.any(|m| m.model_ref.replace("@main", "").contains(&model_owned))
})
.await
.unwrap_or(false);
if installed {
.unwrap_or(false)
}
async fn ensure_model_downloaded(model: &str) -> anyhow::Result<()> {
if model_is_installed(model).await {
return Ok(());
}
mesh_llm_host_runtime::models::download_model_ref_with_progress_details(model, true)

View File

@@ -12,6 +12,7 @@ fn pending_client_runtime(
max_vram_gb: None,
join_token: Some("initial-token".to_string()),
mesh_name: None,
relay_url: None,
trusted_owner_ids: None,
};
super::DesktopMeshRuntime {

View File

@@ -153,6 +153,13 @@ fn should_evict_after_probe(
|| consecutive >= DEAD_PROBE_EVICT_THRESHOLD
}
fn requires_process_restart(
mode: crate::mesh_llm::MeshNodeMode,
startup_in_progress: bool,
) -> bool {
startup_in_progress || mode == crate::mesh_llm::MeshNodeMode::Serve
}
/// Probe and, when justified, remove one stale runtime. A closed port is
/// decisive for a foreground agent start; watchdog and ambiguous/unhealthy
/// ports require consecutive failures to avoid restarting on a transient load
@@ -161,22 +168,23 @@ pub(crate) async fn recover_stale_mesh_runtime(
state: &AppState,
urgency: MeshRecoveryUrgency,
) -> MeshRuntimeRecovery {
let (candidate_id, startup_in_progress) = match state.mesh_llm_runtime.lock().await.as_ref() {
Some(runtime) => (runtime.id(), runtime.is_starting().await),
None => {
state.mesh_recovery.reset_probe_streak();
// A cancelled SDK startup can outlive its Buzz-side task briefly
// because the embedded runtime runs on its own thread. Never start
// a replacement merely because the tracked handle is gone: first
// prove the old ingress is either still useful or has released the
// port. This closes the port-conflict loop in #2304.
return match probe_mesh_ingress().await {
MeshIngressProbe::Live => MeshRuntimeRecovery::Live,
MeshIngressProbe::PortClosed => MeshRuntimeRecovery::Absent,
MeshIngressProbe::Unhealthy => MeshRuntimeRecovery::ReleasePending,
};
}
};
let (candidate_id, startup_in_progress, candidate_mode) =
match state.mesh_llm_runtime.lock().await.as_ref() {
Some(runtime) => (runtime.id(), runtime.is_starting().await, runtime.mode()),
None => {
state.mesh_recovery.reset_probe_streak();
// A cancelled SDK startup can outlive its Buzz-side task briefly
// because the embedded runtime runs on its own thread. Never start
// a replacement merely because the tracked handle is gone: first
// prove the old ingress is either still useful or has released the
// port. This closes the port-conflict loop in #2304.
return match probe_mesh_ingress().await {
MeshIngressProbe::Live => MeshRuntimeRecovery::Live,
MeshIngressProbe::PortClosed => MeshRuntimeRecovery::Absent,
MeshIngressProbe::Unhealthy => MeshRuntimeRecovery::ReleasePending,
};
}
};
let probe = probe_mesh_ingress().await;
if probe == MeshIngressProbe::Live {
state.mesh_recovery.reset_probe_streak();
@@ -196,12 +204,13 @@ pub(crate) async fn recover_stale_mesh_runtime(
return MeshRuntimeRecovery::Debouncing;
}
// The pinned SDK does not yield its control handle until the management
// API is ready. Dropping its still-pending start future would detach the
// embedded runtime thread without sending a shutdown request, so Buzz must
// not evict it and race a replacement onto the same ports. A controlled
// app relaunch is the only process-owned cleanup boundary in this state.
if startup_in_progress {
// Never replace a serving runtime in-process. Its native listeners and
// model host are process-owned; stopping it here and then cold-starting a
// client silently disables Share Compute and can race ports 9337/3131.
// Pending client startups have the same ownership problem because the SDK
// has not yielded a shutdown handle yet. In both cases, process restart is
// the only boundary that preserves the configured role safely.
if requires_process_restart(candidate_mode, startup_in_progress) {
state.mesh_recovery.reset_probe_streak();
return MeshRuntimeRecovery::RestartRequired;
}
@@ -241,6 +250,12 @@ pub(crate) async fn recover_stale_mesh_runtime(
pub(crate) async fn rearm_relay_mesh_for_running_agents(app: &AppHandle) -> Result<(), String> {
let state = app.state::<AppState>();
let _rearm_guard = state.mesh_recovery.rearm_lock.lock().await;
let runtime_mode = state
.mesh_llm_runtime
.lock()
.await
.as_ref()
.map(|runtime| runtime.mode());
let recovery = recover_stale_mesh_runtime(&state, MeshRecoveryUrgency::Watchdog).await;
let active_pubkeys = active_managed_agent_pubkeys(&state);
// Mesh participation is resolved through the same definition-authoritative
@@ -254,6 +269,13 @@ pub(crate) async fn rearm_relay_mesh_for_running_agents(app: &AppHandle) -> Resu
| MeshRuntimeRecovery::Debouncing
| MeshRuntimeRecovery::Replaced => return Ok(()),
MeshRuntimeRecovery::RestartRequired => {
if runtime_mode == Some(crate::mesh_llm::MeshNodeMode::Serve) {
eprintln!(
"buzz-mesh: serving ingress failed; restarting Buzz to restore Share Compute without changing roles"
);
app.request_restart();
return Ok(());
}
let records = crate::managed_agents::load_managed_agents(app).unwrap_or_default();
if !records.iter().any(|record| {
running_relay_mesh_model_id(record, &active_pubkeys, &personas, &global).is_some()
@@ -410,6 +432,7 @@ mod tests {
name_pool: Vec::new(),
is_builtin: false,
is_active: true,
shared: false,
source_team: None,
source_team_persona_slug: None,
catalog_source: None,
@@ -481,6 +504,22 @@ mod tests {
assert!(STALE_STOP_TIMEOUT <= Duration::from_secs(15));
}
#[test]
fn failed_serving_runtime_requires_process_restart_instead_of_client_fallback() {
assert!(requires_process_restart(
crate::mesh_llm::MeshNodeMode::Serve,
false
));
assert!(requires_process_restart(
crate::mesh_llm::MeshNodeMode::Client,
true
));
assert!(!requires_process_restart(
crate::mesh_llm::MeshNodeMode::Client,
false
));
}
#[test]
fn only_running_relay_mesh_agents_trigger_rearm() {
let personas: Vec<crate::managed_agents::AgentDefinition> = Vec::new();

View File

@@ -107,7 +107,9 @@ export function MeshComputeSettingsCard() {
// One-shot hardware-aware catalog fetch. Purely additive: when it fails
// (stub build, survey error) the card falls back to the free-text field.
// Keep an empty draft empty so the UI can explicitly ask the member to choose.
// When there is no saved choice, make the curated recommendation the actual
// default so a new member can turn Share Compute on directly. An explicit
// saved draft always wins.
React.useEffect(() => {
let cancelled = false;
(async () => {
@@ -115,6 +117,11 @@ export function MeshComputeSettingsCard() {
const value = await meshModelCatalog();
if (cancelled) return;
setCatalog(value);
setModelInput((current) => {
if (current.trim() !== "" || !value.recommended) return current;
writeDraft(MODEL_DRAFT_STORAGE_KEY, value.recommended);
return value.recommended;
});
} catch {
// Non-fatal — picker just doesn't render.
}

View File

@@ -2890,9 +2890,7 @@ const mockMeshState: {
servingUsage: MockServingUsage;
} = {
admitted: true,
models: [
{ id: "hf://demo/SmolLM2-135M-Instruct-GGUF:Q4_K_M", name: "SmolLM2 135M" },
],
models: [{ id: "Gemma-4-E4B-it-Q4_K_M", name: "Gemma 4 E4B" }],
denyReason: "not a relay member",
nodeState: "off",
nodeMode: null,
@@ -2901,9 +2899,7 @@ const mockMeshState: {
function resetMockMesh() {
mockMeshState.admitted = true;
mockMeshState.models = [
{ id: "hf://demo/SmolLM2-135M-Instruct-GGUF:Q4_K_M", name: "SmolLM2 135M" },
];
mockMeshState.models = [{ id: "Gemma-4-E4B-it-Q4_K_M", name: "Gemma 4 E4B" }];
mockMeshState.denyReason = "not a relay member";
mockMeshState.nodeState = "off";
mockMeshState.nodeMode = null;
@@ -9744,6 +9740,25 @@ export function maybeInstallE2eTauriMocks() {
}
case "mesh_installed_models":
return mockMeshState.models;
case "mesh_model_catalog":
return {
gpuName: "Mock Apple GPU",
vramDisplay: "32 GB",
vramGb: 32,
recommended: "Gemma-4-E4B-it-Q4_K_M",
entries: [
{
name: "Gemma-4-E4B-it-Q4_K_M",
size: "3.5GB",
sizeGb: 3.5,
description: "Buzz-curated local agent model",
fit: "comfortable",
installed: true,
recommended: true,
curated: true,
},
],
};
case "mesh_node_status":
return meshNodeStatus(mockMeshState.nodeState, mockMeshState.nodeMode);
case "mesh_serving_usage":

View File

@@ -15,7 +15,7 @@ type E2eWindow = Window & {
}) => void;
};
test("Share compute has a clear empty state and starts and stops sharing", async ({
test("Share compute selects the curated default and starts and stops sharing", async ({
page,
}) => {
await installMockBridge(page);
@@ -30,22 +30,32 @@ test("Share compute has a clear empty state and starts and stops sharing", async
await expect(card).toContainText(
"Choose a suggested model below, or enter a model reference or local file",
);
await expect(toggle).toBeDisabled();
await model.fill("hf://demo/SmolLM2-135M-Instruct-GGUF:Q4_K_M");
await expect(model).toHaveValue("Gemma-4-E4B-it-Q4_K_M");
await expect(toggle).toBeEnabled();
await expect(card).toContainText(
"Buzz downloads remote models when sharing starts",
);
await expect(toggle).toBeEnabled();
await toggle.click();
await expect(toggle).toBeChecked();
await expect(card).toContainText("Sharing SmolLM2 135M with relay members");
await expect(card).toContainText("Sharing Gemma 4 E4B with relay members");
await expect
.poll(() =>
page.evaluate(() => (window as E2eWindow).__BUZZ_E2E_COMMANDS__ ?? []),
)
.toContain("mesh_start_node");
await expect
.poll(() =>
page.evaluate(
() => (window as E2eWindow).__BUZZ_E2E_COMMAND_PAYLOADS__ ?? [],
),
)
.toContainEqual({
command: "mesh_start_node",
payload: {
request: { mode: "serve", modelId: "Gemma-4-E4B-it-Q4_K_M" },
},
});
await toggle.click();
await expect(toggle).not.toBeChecked();