From 9b09d1ba269bbd53a219f7c1afedc6413c82adaa Mon Sep 17 00:00:00 2001 From: Michael Neale Date: Wed, 29 Jul 2026 17:01:36 +1000 Subject: [PATCH] fix(mesh): make recommended agent model actionable Preserve user token controls through the shared-compute spawn path and preselect the hardware-ranked Gemma recommendation for first-time sharing.\n\nCo-authored-by: Bartok9 \nCo-authored-by: Taksh Signed-off-by: Michael Neale --- .../src/managed_agents/relay_mesh.rs | 91 +++++++++++++++++-- .../src-tauri/src/managed_agents/runtime.rs | 7 +- .../ui/MeshComputeSettingsCard.tsx | 9 +- desktop/src/testing/e2eBridge.ts | 27 ++++-- desktop/tests/e2e/mesh-compute.spec.ts | 22 +++-- 5 files changed, 130 insertions(+), 26 deletions(-) diff --git a/desktop/src-tauri/src/managed_agents/relay_mesh.rs b/desktop/src-tauri/src/managed_agents/relay_mesh.rs index 7a6f5b094..5ea453d82 100644 --- a/desktop/src-tauri/src/managed_agents/relay_mesh.rs +++ b/desktop/src-tauri/src/managed_agents/relay_mesh.rs @@ -44,13 +44,42 @@ pub fn apply_relay_mesh_env( ); // 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()); + // These are defaults, not policy: the effective agent/persona/global env + // may deliberately choose a smaller cap or enable thinking. This function + // runs after those layers during readiness, so never clobber their values. + insert_default_if_unset(env, "BUZZ_AGENT_MAX_OUTPUT_TOKENS", "4096"); + insert_default_if_unset(env, "BUZZ_AGENT_THINKING_EFFORT", "none"); +} + +#[cfg(feature = "mesh-llm")] +fn insert_default_if_unset( + env: &mut std::collections::BTreeMap, + 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, + model: &str, +) -> std::collections::BTreeMap { + 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"))] @@ -82,4 +111,52 @@ mod tests { 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(), "low".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("low") + ); + } + + #[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")); + } } diff --git a/desktop/src-tauri/src/managed_agents/runtime.rs b/desktop/src-tauri/src/managed_agents/runtime.rs index f3b4cb67f..37927961e 100644 --- a/desktop/src-tauri/src/managed_agents/runtime.rs +++ b/desktop/src-tauri/src/managed_agents/runtime.rs @@ -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); diff --git a/desktop/src/features/mesh-compute/ui/MeshComputeSettingsCard.tsx b/desktop/src/features/mesh-compute/ui/MeshComputeSettingsCard.tsx index 76f86f805..fd7550eff 100644 --- a/desktop/src/features/mesh-compute/ui/MeshComputeSettingsCard.tsx +++ b/desktop/src/features/mesh-compute/ui/MeshComputeSettingsCard.tsx @@ -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. } diff --git a/desktop/src/testing/e2eBridge.ts b/desktop/src/testing/e2eBridge.ts index 7b13273c6..038504170 100644 --- a/desktop/src/testing/e2eBridge.ts +++ b/desktop/src/testing/e2eBridge.ts @@ -2887,9 +2887,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, @@ -2898,9 +2896,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; @@ -9736,6 +9732,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": diff --git a/desktop/tests/e2e/mesh-compute.spec.ts b/desktop/tests/e2e/mesh-compute.spec.ts index 7c360368a..b2e8fc28a 100644 --- a/desktop/tests/e2e/mesh-compute.spec.ts +++ b/desktop/tests/e2e/mesh-compute.spec.ts @@ -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();