mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(desktop): unblock UI freeze when entering the create-agent details step
Clicking "Blank agent" or "Use template" froze the entire webview until runtime discovery finished. The details dialog opens immediately, but on mount it fires three Tauri commands that were synchronous — and Tauri 2 runs sync commands on the main thread, so the UI blocked while login shells resolved each agent binary. - desktop/src-tauri/src/commands/agent_discovery.rs: make discover_acp_providers and discover_managed_agent_prereqs async and move their shell-spawning work into tokio::task::spawn_blocking so the main thread never blocks; both now return Result to surface a panicked task instead of unwrapping - desktop/src-tauri/src/commands/agent_providers.rs: same treatment for discover_backend_providers, which scans every PATH directory on disk for buzz-backend-* binaries - desktop/src/features/agents/ui/CreateAgentDialog.tsx: show a spinner in the submit button while discovery is pending, so the disabled button reads as "loading" rather than broken No frontend API changes needed — invokeTauri handles the Result return transparently. Also fixes the same freeze anywhere these queries fire (Edit Agent, Add Channel Bot, Settings > Doctor). Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
This commit is contained in:
@@ -20,9 +20,16 @@ fn active_installs() -> &'static std::sync::Mutex<std::collections::HashSet<Stri
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn discover_acp_providers() -> Vec<AcpRuntimeCatalogEntry> {
|
||||
crate::managed_agents::clear_resolve_cache();
|
||||
crate::managed_agents::discover_acp_runtimes()
|
||||
pub async fn discover_acp_providers() -> Result<Vec<AcpRuntimeCatalogEntry>, String> {
|
||||
// Discovery spawns login shells to resolve each runtime binary, which can
|
||||
// take seconds. Run it off the main thread so the webview stays responsive
|
||||
// (sync Tauri commands block the main thread).
|
||||
tokio::task::spawn_blocking(|| {
|
||||
crate::managed_agents::clear_resolve_cache();
|
||||
crate::managed_agents::discover_acp_runtimes()
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("runtime discovery task panicked: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -292,26 +299,32 @@ fn floor_char_boundary(s: &str, mut index: usize) -> usize {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn discover_managed_agent_prereqs(
|
||||
pub async fn discover_managed_agent_prereqs(
|
||||
input: DiscoverManagedAgentPrereqsRequest,
|
||||
) -> ManagedAgentPrereqsInfo {
|
||||
let acp_command = input
|
||||
.acp_command
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or(DEFAULT_ACP_COMMAND);
|
||||
let mcp_command = input
|
||||
.mcp_command
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or("");
|
||||
) -> Result<ManagedAgentPrereqsInfo, String> {
|
||||
// Command resolution can spawn a login shell on cache miss — keep it off
|
||||
// the main thread so the webview stays responsive.
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let acp_command = input
|
||||
.acp_command
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or(DEFAULT_ACP_COMMAND);
|
||||
let mcp_command = input
|
||||
.mcp_command
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or("");
|
||||
|
||||
ManagedAgentPrereqsInfo {
|
||||
acp: command_availability(acp_command),
|
||||
mcp: command_availability(mcp_command),
|
||||
}
|
||||
ManagedAgentPrereqsInfo {
|
||||
acp: command_availability(acp_command),
|
||||
mcp: command_availability(mcp_command),
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("prereq discovery task panicked: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
use crate::managed_agents::{discover_provider_candidates, invoke_provider, BackendProviderInfo};
|
||||
|
||||
#[tauri::command]
|
||||
pub fn discover_backend_providers() -> Vec<BackendProviderInfo> {
|
||||
discover_provider_candidates()
|
||||
.into_iter()
|
||||
.map(|(id, path)| BackendProviderInfo {
|
||||
id,
|
||||
binary_path: path.display().to_string(),
|
||||
})
|
||||
.collect()
|
||||
pub async fn discover_backend_providers() -> Result<Vec<BackendProviderInfo>, String> {
|
||||
// PATH scanning hits the filesystem for every directory — run it off the
|
||||
// main thread so the webview stays responsive.
|
||||
tokio::task::spawn_blocking(|| {
|
||||
discover_provider_candidates()
|
||||
.into_iter()
|
||||
.map(|(id, path)| BackendProviderInfo {
|
||||
id,
|
||||
binary_path: path.display().to_string(),
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("provider discovery task panicked: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AlertTriangle, ChevronDown } from "lucide-react";
|
||||
import { AlertTriangle, ChevronDown, Loader2 } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
import {
|
||||
@@ -791,6 +791,9 @@ export function CreateAgentDialog({
|
||||
size="sm"
|
||||
type="button"
|
||||
>
|
||||
{isDiscoveryPending ? (
|
||||
<Loader2 aria-hidden className="h-4 w-4 animate-spin" />
|
||||
) : null}
|
||||
{meshPreparing
|
||||
? "Connecting to mesh..."
|
||||
: createMutation.isPending
|
||||
|
||||
Reference in New Issue
Block a user