diff --git a/desktop/src/features/agents/hooks.ts b/desktop/src/features/agents/hooks.ts index 02b9129ba4..ed46809785 100644 --- a/desktop/src/features/agents/hooks.ts +++ b/desktop/src/features/agents/hooks.ts @@ -125,16 +125,17 @@ function invalidateManagedAgentQueriesInBackground( ); } -export function useAcpRuntimesQuery() { +export function useAcpRuntimesQuery(options?: { enabled?: boolean }) { return useQuery({ + enabled: options?.enabled ?? true, queryKey: acpRuntimesQueryKey, queryFn: discoverAcpRuntimes, staleTime: 60_000, }); } -export function useAvailableAcpRuntimes() { - const query = useAcpRuntimesQuery(); +export function useAvailableAcpRuntimes(options?: { enabled?: boolean }) { + const query = useAcpRuntimesQuery(options); const available = React.useMemo( () => (query.data ?? []).filter( @@ -155,8 +156,9 @@ export function useInstallAcpRuntimeMutation() { }); } -export function useBackendProvidersQuery() { +export function useBackendProvidersQuery(options?: { enabled?: boolean }) { return useQuery({ + enabled: options?.enabled ?? true, queryKey: backendProvidersQueryKey, queryFn: discoverBackendProviders, staleTime: 30_000, @@ -175,11 +177,13 @@ export function usePersonasQuery() { export function useManagedAgentPrereqsQuery( acpCommand: string, mcpCommand: string, + options?: { enabled?: boolean }, ) { const normalizedAcpCommand = acpCommand.trim(); const normalizedMcpCommand = mcpCommand.trim(); return useQuery({ + enabled: options?.enabled ?? true, queryKey: [ ...managedAgentPrereqsQueryKey, normalizedAcpCommand, diff --git a/desktop/src/features/agents/ui/AgentsView.tsx b/desktop/src/features/agents/ui/AgentsView.tsx index 639bd3569f..cc238ba501 100644 --- a/desktop/src/features/agents/ui/AgentsView.tsx +++ b/desktop/src/features/agents/ui/AgentsView.tsx @@ -180,218 +180,246 @@ export function AgentsView() { - { - agents.setLogAgentPubkey(result.agent.pubkey); - agents.setCreatedAgent(result); - }} - onOpenChange={agents.setIsCreateOpen} - open={agents.isCreateOpen} - /> - { - if (!open) { - agents.setAgentToAddToChannel(null); + {agents.isCreateOpen ? ( + { + agents.setLogAgentPubkey(result.agent.pubkey); + agents.setCreatedAgent(result); + }} + onOpenChange={agents.setIsCreateOpen} + open={agents.isCreateOpen} + /> + ) : null} + {agents.agentToAddToChannel ? ( + { + if (!open) { + agents.setAgentToAddToChannel(null); + } + }} + open={agents.agentToAddToChannel !== null} + /> + ) : null} + {agents.createdAgent ? ( + { + if (!open) { + agents.setCreatedAgent(null); + } + }} + /> + ) : null} + {personas.personaDialogState ? ( + - { - if (!open) { - agents.setCreatedAgent(null); + initialValues={personas.personaDialogState.initialValues} + isImportPending={ + personas.personaImportActions.isApplyingPersonaImportUpdate } - }} - /> - { + if (!open) { + personas.setPersonaDialogState(null); + } + }} + onSubmit={personas.handleSubmit} + open={personas.personaDialogState !== null} + submitLabel={personas.personaDialogState.submitLabel} + title={personas.personaDialogState.title} + /> + ) : null} + {personas.personaToDelete ? ( + { + void personas.handleDelete(persona); + }} + onOpenChange={(open) => { + if (!open) { + personas.setPersonaToDelete(null); + } + }} + open={personas.personaToDelete !== null} + persona={personas.personaToDelete} + /> + ) : null} + {personas.isCatalogDialogOpen ? ( + { - if (!open) { - personas.setPersonaDialogState(null); } - }} - onSubmit={personas.handleSubmit} - open={personas.personaDialogState !== null} - submitLabel={personas.personaDialogState?.submitLabel ?? "Save"} - title={personas.personaDialogState?.title ?? "Persona"} - /> - { - void personas.handleDelete(persona); - }} - onOpenChange={(open) => { - if (!open) { - personas.setPersonaToDelete(null); + feedbackErrorMessage={ + personas.personaFeedbackSurface === "catalog" + ? personas.personaErrorMessage + : null } - }} - open={personas.personaToDelete !== null} - persona={personas.personaToDelete} - /> - { - personas.clearFeedback("catalog"); - }} - onOpenChange={personas.setIsCatalogDialogOpen} - onSelectPersona={(persona, active) => { - void personas.handleSetActive(persona, active, "catalog"); - }} - open={personas.isCatalogDialogOpen} - personas={personas.catalogPersonas} - /> - { - if (!open) { - teamActions.setTeamDialogState(null); } - }} - onDeleteRemovedPersonas={teamActions.handleDeleteRemovedPersonas} - onSubmit={teamActions.handleTeamSubmit} - open={teamActions.teamDialogState !== null} - personas={personas.libraryPersonas} - submitLabel={teamActions.teamDialogState?.submitLabel ?? "Save"} - title={teamActions.teamDialogState?.title ?? "Team"} - /> - { - void teamActions.handleDeleteTeam(team); - }} - onOpenChange={(open) => { - if (!open) { - teamActions.setTeamToDelete(null); + isLoading={personas.personasQuery.isLoading} + isPending={personas.setPersonaActiveMutation.isPending} + onClearFeedback={() => { + personas.clearFeedback("catalog"); + }} + onOpenChange={personas.setIsCatalogDialogOpen} + onSelectPersona={(persona, active) => { + void personas.handleSetActive(persona, active, "catalog"); + }} + open={personas.isCatalogDialogOpen} + personas={personas.catalogPersonas} + /> + ) : null} + {teamActions.teamDialogState ? ( + - { - if (!open) { - teamActions.setTeamToAddToChannel(null); + onImportUpdateFile={teamActions.handleEditDialogImportUpdateFile} + onOpenChange={(open) => { + if (!open) { + teamActions.setTeamDialogState(null); + } + }} + onDeleteRemovedPersonas={teamActions.handleDeleteRemovedPersonas} + onSubmit={teamActions.handleTeamSubmit} + open={teamActions.teamDialogState !== null} + personas={personas.libraryPersonas} + submitLabel={teamActions.teamDialogState.submitLabel} + title={teamActions.teamDialogState.title} + /> + ) : null} + {teamActions.teamToDelete ? ( + { + void teamActions.handleDeleteTeam(team); + }} + onOpenChange={(open) => { + if (!open) { + teamActions.setTeamToDelete(null); + } + }} + open={teamActions.teamToDelete !== null} + team={teamActions.teamToDelete} + /> + ) : null} + {teamActions.teamToAddToChannel ? ( + { + if (!open) { + teamActions.setTeamToAddToChannel(null); + } + }} + open={teamActions.teamToAddToChannel !== null} + personas={personas.libraryPersonas} + team={teamActions.teamToAddToChannel} + /> + ) : null} + {personas.batchImportResult ? ( + { + if (!open) { + personas.setBatchImportResult(null); + } + }} + open={personas.batchImportResult !== null} + result={personas.batchImportResult} + /> + ) : null} + {teamActions.teamImportPreview ? ( + { + if (!open) { + teamActions.setTeamImportPreview(null); + } + }} + open={teamActions.teamImportPreview !== null} + preview={teamActions.teamImportPreview.preview} + /> + ) : null} + {teamActions.teamImportTarget ? ( + - { - if (!open) { - personas.setBatchImportResult(null); + onApply={teamActions.handleTeamImportUpdateApply} + onClear={teamActions.clearImportUpdateAndReturnToEdit} + onOpenChange={(open) => { + if (!open) { + teamActions.closeImportUpdateDialog(); + } + }} + open={teamActions.teamImportTarget !== null} + personas={personas.libraryPersonas} + preview={teamActions.teamImportTargetPreview?.preview ?? null} + team={teamActions.teamImportTarget} + /> + ) : null} + {personas.personaImportActions.personaImportTarget ? ( + - { - if (!open) { - teamActions.setTeamImportPreview(null); + isPending={ + personas.personaImportActions.isApplyingPersonaImportUpdate || + personas.updatePersonaMutation.isPending } - }} - open={teamActions.teamImportPreview !== null} - preview={teamActions.teamImportPreview?.preview ?? null} - /> - { - if (!open) { - teamActions.closeImportUpdateDialog(); + onApply={personas.personaImportActions.handleImportUpdateApply} + onClear={ + personas.personaImportActions.clearImportUpdateAndReturnToEdit } - }} - open={teamActions.teamImportTarget !== null} - personas={personas.libraryPersonas} - preview={teamActions.teamImportTargetPreview?.preview ?? null} - team={teamActions.teamImportTarget} - /> - { - if (!open) { - personas.personaImportActions.closeImportUpdateDialog(); + onOpenChange={(open) => { + if (!open) { + personas.personaImportActions.closeImportUpdateDialog(); + } + }} + open={personas.personaImportActions.personaImportTarget !== null} + persona={personas.personaImportActions.personaImportTarget} + preview={ + personas.personaImportActions.personaImportTargetPreview?.preview ?? + null } - }} - open={personas.personaImportActions.personaImportTarget !== null} - persona={personas.personaImportActions.personaImportTarget} - preview={ - personas.personaImportActions.personaImportTargetPreview?.preview ?? - null - } - /> + /> + ) : null} ); } diff --git a/desktop/src/features/agents/ui/CreateAgentDialog.tsx b/desktop/src/features/agents/ui/CreateAgentDialog.tsx index 52e74ad1a4..16e67dcf68 100644 --- a/desktop/src/features/agents/ui/CreateAgentDialog.tsx +++ b/desktop/src/features/agents/ui/CreateAgentDialog.tsx @@ -52,16 +52,18 @@ export function CreateAgentDialog({ onOpenChange: (open: boolean) => void; }) { const createMutation = useCreateManagedAgentMutation(); - const providersQuery = useAvailableAcpRuntimes(); - const allProvidersQuery = useAcpRuntimesQuery(); - const backendProvidersQuery = useBackendProvidersQuery(); + const providersQuery = useAvailableAcpRuntimes({ enabled: open }); + const allProvidersQuery = useAcpRuntimesQuery({ enabled: open }); + const backendProvidersQuery = useBackendProvidersQuery({ enabled: open }); const { lastRuntimeId, setLastRuntime } = useLastRuntime(); const [acpCommand, setAcpCommand] = React.useState("buzz-acp"); const [agentCommand, setAgentCommand] = React.useState("buzz-agent"); const [agentArgs, setAgentArgs] = React.useState("acp"); const [mcpCommand, setMcpCommand] = React.useState(""); const [mcpToolsets, setMcpToolsets] = React.useState(""); - const prereqsQuery = useManagedAgentPrereqsQuery(acpCommand, mcpCommand); + const prereqsQuery = useManagedAgentPrereqsQuery(acpCommand, mcpCommand, { + enabled: open, + }); const [name, setName] = React.useState(""); const [relayUrl, setRelayUrl] = React.useState(""); const [spawnAfterCreate, setSpawnAfterCreate] = React.useState(true); diff --git a/desktop/src/features/agents/ui/ManagedAgentRow.tsx b/desktop/src/features/agents/ui/ManagedAgentRow.tsx index 556f83a722..33ca2e5bbb 100644 --- a/desktop/src/features/agents/ui/ManagedAgentRow.tsx +++ b/desktop/src/features/agents/ui/ManagedAgentRow.tsx @@ -535,11 +535,13 @@ function AgentActionsMenu({ - + {editOpen ? ( + + ) : null} ); } diff --git a/desktop/src/features/agents/ui/useManagedAgentActions.ts b/desktop/src/features/agents/ui/useManagedAgentActions.ts index 7d8013791c..432544ee27 100644 --- a/desktop/src/features/agents/ui/useManagedAgentActions.ts +++ b/desktop/src/features/agents/ui/useManagedAgentActions.ts @@ -31,7 +31,8 @@ import { export function useManagedAgentActions() { const relayAgentsQuery = useRelayAgentsQuery(); const managedAgentsQuery = useManagedAgentsQuery(); - const channelsQuery = useChannelsQuery(); + const [shouldLoadChannels, setShouldLoadChannels] = React.useState(false); + const channelsQuery = useChannelsQuery({ enabled: shouldLoadChannels }); const startMutation = useStartManagedAgentMutation(); const stopMutation = useStopManagedAgentMutation(); const deleteMutation = useDeleteManagedAgentMutation(); @@ -53,6 +54,13 @@ export function useManagedAgentActions() { const managedAgentLogQuery = useManagedAgentLogQuery(logAgentPubkey); + React.useEffect(() => { + const timeoutId = window.setTimeout(() => { + setShouldLoadChannels(true); + }, 0); + return () => window.clearTimeout(timeoutId); + }, []); + const managedAgents = React.useMemo( () => [...(managedAgentsQuery.data ?? [])].sort((left, right) => { @@ -151,14 +159,24 @@ export function useManagedAgentActions() { } } + async function getChannelsForAction() { + if (channelsQuery.data) { + return channelsQuery.data; + } + + const result = await channelsQuery.refetch(); + return result.data ?? []; + } + async function handleStop(pubkey: string) { clearFeedback(); try { const agent = managedAgents.find((a) => a.pubkey === pubkey); if (!agent) return; + const channels = await getChannelsForAction(); const result = await stopManagedAgentWithRules({ agent, - channels: channelsQuery.data ?? [], + channels, relayAgents: relayAgentsQuery.data ?? [], stopManagedAgent: stopMutation.mutateAsync, }); @@ -193,9 +211,10 @@ export function useManagedAgentActions() { try { const agent = managedAgents.find((a) => a.pubkey === pubkey); if (!agent) return; + const channels = await getChannelsForAction(); const result = await deleteManagedAgentWithRules({ agent, - channels: channelsQuery.data ?? [], + channels, deleteManagedAgent: deleteMutation.mutateAsync, presenceLookup: managedPresenceQuery.data, relayAgents: relayAgentsQuery.data ?? [], diff --git a/desktop/src/features/agents/ui/usePersonaActions.ts b/desktop/src/features/agents/ui/usePersonaActions.ts index 478c57ebd2..e8f1930e1a 100644 --- a/desktop/src/features/agents/ui/usePersonaActions.ts +++ b/desktop/src/features/agents/ui/usePersonaActions.ts @@ -36,7 +36,11 @@ type PersonaFeedbackSurface = "catalog" | "library"; export function usePersonaActions() { const queryClient = useQueryClient(); const personasQuery = usePersonasQuery(); - const acpRuntimesQuery = useAcpRuntimesQuery(); + const [shouldLoadAcpRuntimes, setShouldLoadAcpRuntimes] = + React.useState(false); + const acpRuntimesQuery = useAcpRuntimesQuery({ + enabled: shouldLoadAcpRuntimes, + }); const createPersonaMutation = useCreatePersonaMutation(); const updatePersonaMutation = useUpdatePersonaMutation(); const deletePersonaMutation = useDeletePersonaMutation(); @@ -141,6 +145,7 @@ export function usePersonaActions() { try { const result = await parsePersonaFiles(fileBytes, fileName); if (isSingleItemFile(fileBytes) && result.personas.length === 1) { + setShouldLoadAcpRuntimes(true); setPersonaDialogState(importPersonaDialogState(result.personas[0])); } else if (result.personas.length > 0) { setBatchImportResult(result); @@ -182,16 +187,19 @@ export function usePersonaActions() { function openCreate() { clearFeedback("library"); + setShouldLoadAcpRuntimes(true); setPersonaDialogState(createPersonaDialogState()); } function openEdit(persona: AgentPersona) { clearFeedback("library"); + setShouldLoadAcpRuntimes(true); setPersonaDialogState(editPersonaDialogState(persona)); } function openDuplicate(persona: AgentPersona) { clearFeedback("library"); + setShouldLoadAcpRuntimes(true); setPersonaDialogState(duplicatePersonaDialogState(persona)); } diff --git a/desktop/src/features/channels/hooks.ts b/desktop/src/features/channels/hooks.ts index 25994aa022..b6d4d7299d 100644 --- a/desktop/src/features/channels/hooks.ts +++ b/desktop/src/features/channels/hooks.ts @@ -101,8 +101,9 @@ function setChannelArchivedState( ); } -export function useChannelsQuery() { +export function useChannelsQuery(options?: { enabled?: boolean }) { return useQuery({ + enabled: options?.enabled ?? true, queryKey: channelsQueryKey, queryFn: async () => sortChannels(await getChannels()), staleTime: 60_000,