Delete persona-backed agents from profile

This commit is contained in:
klopez4212
2026-06-22 15:43:27 +01:00
parent 71501c2b39
commit facace3277
4 changed files with 181 additions and 41 deletions
@@ -28,11 +28,11 @@ export function PersonaDeleteDialog({
<AlertDialog onOpenChange={onOpenChange} open={open}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete persona?</AlertDialogTitle>
<AlertDialogTitle>Delete agent?</AlertDialogTitle>
<AlertDialogDescription>
{persona
? `Delete ${persona.displayName}. Existing agents keep their copied settings, but this template will no longer be available for new deployments.`
: "Delete this persona."}
? `Delete ${persona.displayName} and any managed instance tied to it. This cannot be undone.`
: "Delete this agent."}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
@@ -31,7 +31,6 @@ import { AddAgentToChannelDialog } from "@/features/agents/ui/AddAgentToChannelD
import { useActiveAgentTurnsBridge } from "@/features/agents/activeAgentTurnsStore";
import { resolvePersonaRuntime } from "@/features/agents/lib/resolvePersonaRuntime";
import {
deleteManagedAgentWithRules,
isManagedAgentActive,
startManagedAgentWithRules,
stopManagedAgentWithRules,
@@ -63,12 +62,12 @@ import {
ModelFocusedView,
ProfileSummaryView,
} from "@/features/profile/ui/UserProfilePanelSections";
import { useProfileAgentDeletion } from "@/features/profile/ui/UserProfilePanelDeletion";
import { useProfileFieldBuckets } from "@/features/profile/ui/UserProfilePanelFields";
import { UserProfilePersonaDialogs } from "@/features/profile/ui/UserProfilePersonaDialogs";
import {
buildPersonaDraftProfile,
deriveProfileChannels,
getRelayAgentChannelIds,
PROFILE_PANEL_VIEW_TITLES,
type ProfilePanelView,
resolveAgentInstruction,
@@ -82,7 +81,6 @@ import { useAgentSession } from "@/shared/context/AgentSessionContext";
import { useEscapeKey } from "@/shared/hooks/useEscapeKey";
import { useIsThreadPanelOverlay } from "@/shared/hooks/use-mobile";
import { THREAD_PANEL_MIN_WIDTH_PX } from "@/shared/hooks/useThreadPanelWidth";
import { removeChannelMember } from "@/shared/api/tauri";
import {
AuxiliaryPanelHeader,
AuxiliaryPanelHeaderGroup,
@@ -327,21 +325,18 @@ export function UserProfilePanel({
setEditAgentOpen(true);
}, [resolvedPersona]);
const removeAgentFromAllChannels = React.useCallback(
async (agentPubkey: string) => {
const channelIds = getRelayAgentChannelIds(
relayAgentsQuery.data,
agentPubkey,
);
if (channelIds.length === 0) return;
await Promise.allSettled(
channelIds.map((channelId) =>
removeChannelMember(channelId, agentPubkey),
),
);
},
[relayAgentsQuery.data],
);
const {
deleteManagedAgentRecord,
deleteManagedAgentsForPersona,
removeAgentFromAllChannels,
} = useProfileAgentDeletion({
channels: channelsQuery.data,
deleteManagedAgent: deleteAgentMutation.mutateAsync,
managedAgent,
managedAgents: managedAgentsQuery.data,
presenceLookup: presenceQuery.data,
relayAgents: relayAgentsQuery.data,
});
const handleAgentPrimaryAction = React.useCallback(async () => {
if (!managedAgent) return;
@@ -480,16 +475,9 @@ export function UserProfilePanel({
if (!managedAgent) return;
try {
const result = await deleteManagedAgentWithRules({
agent: managedAgent,
channels: channelsQuery.data ?? [],
deleteManagedAgent: deleteAgentMutation.mutateAsync,
presenceLookup: presenceQuery.data,
relayAgents: relayAgentsQuery.data ?? [],
});
const result = await deleteManagedAgentRecord(managedAgent);
if (result.cancelled) return;
await removeAgentFromAllChannels(managedAgent.pubkey);
toast.success(`Deleted ${managedAgent.name}.`);
onClose();
} catch (error) {
@@ -497,15 +485,7 @@ export function UserProfilePanel({
error instanceof Error ? error.message : "Failed to delete agent.",
);
}
}, [
channelsQuery.data,
deleteAgentMutation.mutateAsync,
managedAgent,
onClose,
presenceQuery.data,
relayAgentsQuery.data,
removeAgentFromAllChannels,
]);
}, [deleteManagedAgentRecord, managedAgent, onClose]);
const handleSubmitPersona = React.useCallback(
async (input: CreatePersonaInput | UpdatePersonaInput) => {
@@ -563,6 +543,10 @@ export function UserProfilePanel({
if (resolvedPersona.isBuiltIn) {
try {
const deletedInstances =
await deleteManagedAgentsForPersona(resolvedPersona);
if (!deletedInstances) return;
await setPersonaActiveMutation.mutateAsync({
id: resolvedPersona.id,
active: false,
@@ -583,11 +567,20 @@ export function UserProfilePanel({
}
setPersonaToDelete(resolvedPersona);
}, [onClose, resolvedPersona, setPersonaActiveMutation.mutateAsync]);
}, [
deleteManagedAgentsForPersona,
onClose,
resolvedPersona,
setPersonaActiveMutation.mutateAsync,
]);
const handleConfirmDeletePersona = React.useCallback(
async (personaToConfirm: AgentPersona) => {
try {
const deletedInstances =
await deleteManagedAgentsForPersona(personaToConfirm);
if (!deletedInstances) return;
await deletePersonaMutation.mutateAsync(personaToConfirm.id);
toast.success(`Deleted ${personaToConfirm.displayName}.`);
setPersonaToDelete(null);
@@ -598,7 +591,7 @@ export function UserProfilePanel({
);
}
},
[deletePersonaMutation.mutateAsync, onClose],
[deleteManagedAgentsForPersona, deletePersonaMutation.mutateAsync, onClose],
);
const handleAddedToChannel = React.useCallback(
@@ -0,0 +1,147 @@
import * as React from "react";
import {
deleteManagedAgentWithRules,
type ManagedAgentActionResult,
} from "@/features/agents/lib/managedAgentControlActions";
import { removeChannelMember } from "@/shared/api/tauri";
import type {
AgentPersona,
Channel,
ManagedAgent,
PresenceLookup,
RelayAgent,
} from "@/shared/api/types";
import { getRelayAgentChannelIds } from "@/features/profile/ui/UserProfilePanelUtils";
type DeleteManagedAgentRulesContext = Omit<
Parameters<typeof deleteManagedAgentWithRules>[0],
"agent"
>;
type DeleteProfileManagedAgentContext = DeleteManagedAgentRulesContext & {
removeAgentFromAllChannels: (pubkey: string) => Promise<void>;
};
type DeleteProfileManagedAgentsForPersonaContext =
DeleteProfileManagedAgentContext & {
managedAgents: readonly ManagedAgent[];
selectedAgent?: ManagedAgent;
};
type UseProfileAgentDeletionInput = {
channels?: readonly Channel[];
deleteManagedAgent: DeleteManagedAgentRulesContext["deleteManagedAgent"];
managedAgent?: ManagedAgent;
managedAgents?: readonly ManagedAgent[];
presenceLookup?: PresenceLookup | null;
relayAgents?: readonly RelayAgent[];
};
export function useProfileAgentDeletion({
channels,
deleteManagedAgent,
managedAgent,
managedAgents,
presenceLookup,
relayAgents,
}: UseProfileAgentDeletionInput) {
const removeAgentFromAllChannels = React.useCallback(
async (agentPubkey: string) => {
const channelIds = getRelayAgentChannelIds(relayAgents, agentPubkey);
if (channelIds.length === 0) return;
await Promise.allSettled(
channelIds.map((channelId) =>
removeChannelMember(channelId, agentPubkey),
),
);
},
[relayAgents],
);
const deleteManagedAgentRecord = React.useCallback(
(agentToDelete: ManagedAgent) =>
deleteProfileManagedAgent(agentToDelete, {
channels: channels ?? [],
deleteManagedAgent,
presenceLookup,
relayAgents: relayAgents ?? [],
removeAgentFromAllChannels,
}),
[
channels,
deleteManagedAgent,
presenceLookup,
relayAgents,
removeAgentFromAllChannels,
],
);
const deleteManagedAgentsForPersona = React.useCallback(
(persona: AgentPersona) =>
deleteProfileManagedAgentsForPersona(persona, {
channels: channels ?? [],
deleteManagedAgent,
managedAgents: managedAgents ?? [],
presenceLookup,
relayAgents: relayAgents ?? [],
removeAgentFromAllChannels,
selectedAgent: managedAgent,
}),
[
channels,
deleteManagedAgent,
managedAgent,
managedAgents,
presenceLookup,
relayAgents,
removeAgentFromAllChannels,
],
);
return {
deleteManagedAgentRecord,
deleteManagedAgentsForPersona,
removeAgentFromAllChannels,
};
}
export async function deleteProfileManagedAgent(
agent: ManagedAgent,
context: DeleteProfileManagedAgentContext,
): Promise<ManagedAgentActionResult> {
const { removeAgentFromAllChannels, ...deleteContext } = context;
const result = await deleteManagedAgentWithRules({
agent,
...deleteContext,
});
if (result.cancelled) return result;
await removeAgentFromAllChannels(agent.pubkey);
return result;
}
export async function deleteProfileManagedAgentsForPersona(
persona: AgentPersona,
context: DeleteProfileManagedAgentsForPersonaContext,
): Promise<ManagedAgentActionResult> {
const { managedAgents, selectedAgent, ...deleteContext } = context;
const agentsByPubkey = new Map<string, ManagedAgent>();
for (const agent of managedAgents) {
if (agent.personaId === persona.id) {
agentsByPubkey.set(agent.pubkey, agent);
}
}
if (selectedAgent?.personaId === persona.id) {
agentsByPubkey.set(selectedAgent.pubkey, selectedAgent);
}
for (const agent of agentsByPubkey.values()) {
const result = await deleteProfileManagedAgent(agent, deleteContext);
if (result.cancelled) return result;
}
return {};
}
@@ -321,7 +321,7 @@ export function ProfileSummaryView({
<UserProfileAgentActions
isPending={isAgentActionPending}
managedAgent={managedAgent}
onDelete={handleDeleteAgent}
onDelete={persona ? handleDeletePersona : handleDeleteAgent}
onDuplicatePersona={handleDuplicatePersona}
onExportPersona={handleExportPersona}
personaActionKey={personaActionKey}