mirror of
https://github.com/block/buzz.git
synced 2026-11-07 18:01:06 +00:00
fix(desktop): clear resolve cache on re-run, per-provider concurrency, dynamic install hints
Re-run button now clears the binary resolve cache so it actually re-probes the filesystem instead of returning stale cached results. Replace global AtomicBool install guard with per-provider Mutex<HashSet<String>> so concurrent installs for different providers (e.g. claude + codex) can proceed in parallel. Add codex CLI install command (npm install -g @openai/codex) so clicking Install when both CLI and adapter are missing installs both. Compute install_hint dynamically based on availability state so the UI accurately describes what the Install button will do.
This commit is contained in:
@@ -50,7 +50,7 @@ const overrides = new Map([
|
||||
["src-tauri/src/commands/messages.rs", 510], // feed multi-query + NIP-50 search + forum thread resolution + thread ref + reactions via REQ
|
||||
["src-tauri/src/nostr_convert.rs", 1150], // 12 Nostr event→model converters (channels, profiles, members, notes, search, agents, relay members) + rank_user_search_results helper for NIP-50 user search + 33 unit tests
|
||||
["src-tauri/src/managed_agents/runtime.rs", 1110], // ... + respond-to gate env (SPROUT_ACP_RESPOND_TO[_ALLOWLIST]) + per-mode env builder + tests + persona/agent env_vars spawn merge (helper + tests now in env_vars.rs)
|
||||
["src-tauri/src/managed_agents/discovery.rs", 610], // KNOWN_ACP_PROVIDERS catalog + resolve_command cache + login_shell_path + classify_provider + discover_acp_providers (three-state: Available/AdapterMissing/NotInstalled) + known_acp_provider/known_acp_provider_exact + normalize_agent_args + 14 unit tests
|
||||
["src-tauri/src/managed_agents/discovery.rs", 645], // KNOWN_ACP_PROVIDERS catalog + resolve_command cache + login_shell_path + classify_provider + discover_acp_providers (three-state with dynamic install_hint) + known_acp_provider/known_acp_provider_exact + normalize_agent_args + 14 unit tests
|
||||
["src-tauri/src/managed_agents/types.rs", 745], // ManagedAgentRecord/Summary + Create/Update request structs + AcpProviderCatalogEntry + InstallRuntimeResult + RespondTo enum + validate_respond_to_allowlist + tests + persona/agent env_vars field
|
||||
["src-tauri/src/managed_agents/backend.rs", 700], // provider IPC, validation, discovery, binary resolution + tests + redact_secrets_with for user env values + env_secrets_from_request + redact_env_values_in (shared with model discovery)
|
||||
["src/features/huddle/HuddleContext.tsx", 650], // huddle lifecycle context + joinHuddle + connectAndSetupMedia shared helper + activeSpeakers/isReconnecting state + PTT (reusable AudioContext) + TTS subscription + mic level analyser (10fps throttle) + agent pubkey refresh
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::io::Read;
|
||||
use std::sync::atomic::Ordering::{Acquire, Relaxed};
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
use crate::{
|
||||
@@ -13,11 +12,16 @@ use crate::{
|
||||
relay::query_relay,
|
||||
};
|
||||
|
||||
static INSTALL_IN_PROGRESS: std::sync::atomic::AtomicBool =
|
||||
std::sync::atomic::AtomicBool::new(false);
|
||||
fn active_installs() -> &'static std::sync::Mutex<std::collections::HashSet<String>> {
|
||||
use std::collections::HashSet;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
static ACTIVE: OnceLock<Mutex<HashSet<String>>> = OnceLock::new();
|
||||
ACTIVE.get_or_init(|| Mutex::new(HashSet::new()))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn discover_acp_providers() -> Vec<AcpProviderCatalogEntry> {
|
||||
crate::managed_agents::clear_resolve_cache();
|
||||
crate::managed_agents::discover_acp_providers()
|
||||
}
|
||||
|
||||
@@ -31,18 +35,25 @@ pub async fn install_acp_runtime(provider_id: String) -> Result<InstallRuntimeRe
|
||||
/// Err(_) = infrastructure failure (panic, concurrency guard).
|
||||
/// Ok({success: false}) = an install step failed (stderr captured in steps).
|
||||
fn install_acp_runtime_blocking(provider_id: &str) -> Result<InstallRuntimeResult, String> {
|
||||
// Prevent concurrent installs.
|
||||
INSTALL_IN_PROGRESS
|
||||
.compare_exchange(false, true, Acquire, Relaxed)
|
||||
.map_err(|_| "an install is already in progress".to_string())?;
|
||||
|
||||
struct Guard;
|
||||
impl Drop for Guard {
|
||||
fn drop(&mut self) {
|
||||
INSTALL_IN_PROGRESS.store(false, std::sync::atomic::Ordering::Release);
|
||||
// Prevent concurrent installs for the same provider.
|
||||
{
|
||||
let mut set = active_installs()
|
||||
.lock()
|
||||
.map_err(|_| "install lock poisoned".to_string())?;
|
||||
if !set.insert(provider_id.to_string()) {
|
||||
return Err(format!("an install is already in progress for {provider_id}"));
|
||||
}
|
||||
}
|
||||
let _guard = Guard;
|
||||
|
||||
struct Guard(String);
|
||||
impl Drop for Guard {
|
||||
fn drop(&mut self) {
|
||||
if let Ok(mut set) = active_installs().lock() {
|
||||
set.remove(&self.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
let _guard = Guard(provider_id.to_string());
|
||||
|
||||
let provider = crate::managed_agents::known_acp_provider_exact(provider_id)
|
||||
.ok_or_else(|| format!("unknown provider: {provider_id}"))?;
|
||||
|
||||
@@ -95,7 +95,7 @@ const KNOWN_ACP_PROVIDERS: &[KnownAcpProvider] = &[
|
||||
mcp_command: None,
|
||||
mcp_hooks: false,
|
||||
underlying_cli: Some("codex"),
|
||||
cli_install_commands: &[],
|
||||
cli_install_commands: &["npm install -g @openai/codex"],
|
||||
adapter_install_commands: &["npm install -g @zed-industries/codex-acp"],
|
||||
install_instructions_url: "https://github.com/zed-industries/codex-acp",
|
||||
install_hint: "Install the Codex ACP adapter via npm.",
|
||||
@@ -446,6 +446,40 @@ pub fn discover_acp_providers() -> Vec<AcpProviderCatalogEntry> {
|
||||
let can_auto_install = !provider.cli_install_commands.is_empty()
|
||||
|| !provider.adapter_install_commands.is_empty();
|
||||
|
||||
let install_hint = if availability == AcpAvailabilityStatus::Available {
|
||||
provider.install_hint.to_string()
|
||||
} else if provider.underlying_cli.is_some() {
|
||||
let has_cli_cmds = !provider.cli_install_commands.is_empty();
|
||||
let has_adapter_cmds = !provider.adapter_install_commands.is_empty();
|
||||
match availability {
|
||||
AcpAvailabilityStatus::AdapterMissing => {
|
||||
if has_adapter_cmds {
|
||||
format!("Install the {} ACP adapter via the Install button, or follow the manual instructions.", provider.label)
|
||||
} else {
|
||||
provider.install_hint.to_string()
|
||||
}
|
||||
}
|
||||
AcpAvailabilityStatus::NotInstalled => match (has_cli_cmds, has_adapter_cmds) {
|
||||
(true, true) => format!(
|
||||
"Install will set up the {} CLI and its ACP adapter.",
|
||||
provider.label
|
||||
),
|
||||
(true, false) => format!(
|
||||
"Install will set up the {} CLI. Install the ACP adapter manually.",
|
||||
provider.label
|
||||
),
|
||||
(false, true) => format!(
|
||||
"Install will set up the ACP adapter. Install the {} CLI manually.",
|
||||
provider.label
|
||||
),
|
||||
(false, false) => provider.install_hint.to_string(),
|
||||
},
|
||||
AcpAvailabilityStatus::Available => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
provider.install_hint.to_string()
|
||||
};
|
||||
|
||||
AcpProviderCatalogEntry {
|
||||
id: provider.id.to_string(),
|
||||
label: provider.label.to_string(),
|
||||
@@ -455,7 +489,7 @@ pub fn discover_acp_providers() -> Vec<AcpProviderCatalogEntry> {
|
||||
binary_path,
|
||||
default_args,
|
||||
mcp_command: provider.mcp_command.map(str::to_string),
|
||||
install_hint: provider.install_hint.to_string(),
|
||||
install_hint,
|
||||
install_instructions_url: provider.install_instructions_url.to_string(),
|
||||
can_auto_install,
|
||||
underlying_cli_path,
|
||||
|
||||
Reference in New Issue
Block a user