diff --git a/src-tauri/src/acp/types.rs b/src-tauri/src/acp/types.rs index b9084dd..d75895c 100644 --- a/src-tauri/src/acp/types.rs +++ b/src-tauri/src/acp/types.rs @@ -244,6 +244,15 @@ pub struct AcpAgentInfo { pub codex_config_toml: Option, } +/// Lightweight status info for a single agent, used by connect() pre-check. +#[derive(Debug, Clone, Serialize)] +pub struct AcpAgentStatus { + pub agent_type: crate::models::agent::AgentType, + pub available: bool, + pub enabled: bool, + pub installed_version: Option, +} + #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum AgentSkillScope { diff --git a/src-tauri/src/commands/acp.rs b/src-tauri/src/commands/acp.rs index f0e63a1..1e1549d 100644 --- a/src-tauri/src/commands/acp.rs +++ b/src-tauri/src/commands/acp.rs @@ -1127,6 +1127,44 @@ pub async fn acp_list_connections( Ok(manager.list_connections().await) } +#[tauri::command] +pub async fn acp_get_agent_status( + agent_type: AgentType, + db: tauri::State<'_, AppDatabase>, +) -> Result { + let platform = registry::current_platform(); + let meta = registry::get_agent_meta(agent_type); + let setting = agent_setting_service::get_by_agent_type(&db.conn, agent_type) + .await + .map_err(|e| AcpError::protocol(e.to_string()))?; + + let (available, installed_version) = match &meta.distribution { + registry::AgentDistribution::Npx { .. } => ( + true, + setting.as_ref().and_then(|m| m.installed_version.clone()), + ), + registry::AgentDistribution::Binary { + platforms, cmd, .. + } => { + let detected = + binary_cache::detect_installed_version(agent_type, cmd) + .ok() + .flatten(); + ( + platforms.iter().any(|p| p.platform == platform), + detected, + ) + } + }; + + Ok(crate::acp::types::AcpAgentStatus { + agent_type, + available, + enabled: setting.map(|m| m.enabled).unwrap_or(true), + installed_version, + }) +} + #[tauri::command] pub async fn acp_list_agents( db: tauri::State<'_, AppDatabase>, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index c1406e7..c0d974d 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -275,6 +275,7 @@ pub fn run() { acp_commands::acp_disconnect, acp_commands::acp_list_connections, acp_commands::acp_list_agents, + acp_commands::acp_get_agent_status, acp_commands::acp_clear_binary_cache, acp_commands::acp_download_agent_binary, acp_commands::acp_detect_agent_local_version, diff --git a/src/contexts/acp-connections-context.tsx b/src/contexts/acp-connections-context.tsx index 95a0c85..06fb1d1 100644 --- a/src/contexts/acp-connections-context.tsx +++ b/src/contexts/acp-connections-context.tsx @@ -15,7 +15,7 @@ import { disposeTauriListener } from "@/lib/tauri-listener" import { inferLiveToolName } from "@/lib/tool-call-normalization" import { acpConnect, - acpListAgents, + acpGetAgentStatus, acpPrompt, acpSetMode, acpSetConfigOption, @@ -25,7 +25,7 @@ import { } from "@/lib/tauri" import type { AgentType, - AcpAgentInfo, + AcpAgentStatus, AcpEvent, AvailableCommandInfo, ConnectionStatus, @@ -1169,7 +1169,7 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) { ) const resolveAutoLinkBlockState = useCallback( - (agent: AcpAgentInfo | null): AutoLinkBlockState => { + (agent: AcpAgentStatus | null): AutoLinkBlockState => { if (!agent) { return { kind: "missing_config", reason: t("blocked.missingConfig") } } @@ -1685,11 +1685,9 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) { try { if (isAutoLink) { - let configuredAgent: AcpAgentInfo | null = null + let configuredAgent: AcpAgentStatus | null = null try { - const agents = await acpListAgents() - configuredAgent = - agents.find((agent) => agent.agent_type === agentType) ?? null + configuredAgent = await acpGetAgentStatus(agentType) } catch (error) { const reason = t("unableReadAgentConfig", { message: normalizeErrorMessage(error), diff --git a/src/lib/tauri.ts b/src/lib/tauri.ts index c475c6c..42e15ee 100644 --- a/src/lib/tauri.ts +++ b/src/lib/tauri.ts @@ -9,6 +9,7 @@ import type { SidebarData, ConnectionInfo, AcpAgentInfo, + AcpAgentStatus, AgentSkillScope, AgentSkillLayout, AgentSkillItem, @@ -153,6 +154,12 @@ export async function acpListAgents(): Promise { return invoke("acp_list_agents") } +export async function acpGetAgentStatus( + agentType: AgentType +): Promise { + return invoke("acp_get_agent_status", { agentType }) +} + export async function acpClearBinaryCache(agentType: AgentType): Promise { return invoke("acp_clear_binary_cache", { agentType }) } diff --git a/src/lib/types.ts b/src/lib/types.ts index 8341f82..ba0ac31 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -463,6 +463,14 @@ export interface AcpAgentInfo { codex_config_toml: string | null } +// Lightweight agent status returned by acp_get_agent_status +export interface AcpAgentStatus { + agent_type: AgentType + available: boolean + enabled: boolean + installed_version: string | null +} + export type AgentSkillScope = "global" | "project" export type AgentSkillLayout = "markdown_file" | "skill_directory"