feat(vscode): Delete Agent Group (#109)

This commit is contained in:
wienans
2026-01-06 19:46:25 +02:00
committed by GitHub
parent b0bfa739e1
commit af01995c3b
2 changed files with 116 additions and 1 deletions
@@ -6,6 +6,7 @@ import {
RiSearchLine,
RiGitBranchLine,
} from '@remixicon/react';
import { toast } from 'sonner';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
@@ -15,6 +16,14 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { cn } from '@/lib/utils';
import { useAgentGroupsStore, type AgentGroup } from '@/stores/useAgentGroupsStore';
import { useDirectoryStore } from '@/stores/useDirectoryStore';
@@ -41,6 +50,34 @@ interface AgentGroupItemProps {
const AgentGroupItem: React.FC<AgentGroupItemProps> = ({ group, isSelected, onSelect }) => {
const [menuOpen, setMenuOpen] = React.useState(false);
const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
const [isDeleting, setIsDeleting] = React.useState(false);
const deleteGroup = useAgentGroupsStore((state) => state.deleteGroup);
const handleDelete = async () => {
setIsDeleting(true);
try {
const { success, deletedCount, failedCount } = await deleteGroup(group.name);
if (success) {
toast.success(`Deleted agent group "${group.name}"`, {
description: `${deletedCount} session${deletedCount !== 1 ? 's' : ''} removed with worktrees archived.`,
});
} else if (deletedCount > 0) {
toast.warning(`Partially deleted agent group "${group.name}"`, {
description: `${deletedCount} deleted, ${failedCount} failed.`,
});
} else {
toast.error(`Failed to delete agent group "${group.name}"`);
}
} catch (error) {
toast.error(`Failed to delete agent group "${group.name}"`);
console.error('Delete group error:', error);
} finally {
setIsDeleting(false);
setShowDeleteConfirm(false);
}
};
return (
<div
@@ -86,13 +123,47 @@ const AgentGroupItem: React.FC<AgentGroupItemProps> = ({ group, isSelected, onSe
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-[140px]">
<DropdownMenuItem className="text-destructive focus:text-destructive">
<DropdownMenuItem
className="text-destructive focus:text-destructive"
onClick={(e) => {
e.stopPropagation();
setMenuOpen(false);
setShowDeleteConfirm(true);
}}
>
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
<Dialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>
<DialogContent showCloseButton={!isDeleting}>
<DialogHeader>
<DialogTitle>Delete Agent Group</DialogTitle>
<DialogDescription>
Are you sure you want to delete "{group.name}"? This will remove {group.sessionCount} session{group.sessionCount !== 1 ? 's' : ''} and archive their worktrees. This action cannot be undone.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
variant="outline"
onClick={() => setShowDeleteConfirm(false)}
disabled={isDeleting}
>
Cancel
</Button>
<Button
variant="destructive"
onClick={handleDelete}
disabled={isDeleting}
>
{isDeleting ? 'Deleting...' : 'Delete'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
};
@@ -2,6 +2,7 @@ import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
import { opencodeClient } from '@/lib/opencode/client';
import { useDirectoryStore } from './useDirectoryStore';
import { useSessionStore } from './useSessionStore';
import type { WorktreeMetadata } from '@/types/worktree';
import { listWorktrees, mapWorktreeToMetadata } from '@/lib/git/worktreeService';
import type { Session } from '@opencode-ai/sdk/v2';
@@ -72,6 +73,8 @@ interface AgentGroupsActions {
getSelectedGroup: () => AgentGroup | null;
/** Get the currently selected session */
getSelectedSession: () => AgentGroupSession | null;
/** Delete a group and all its sessions, archiving worktrees */
deleteGroup: (groupName: string) => Promise<{ success: boolean; deletedCount: number; failedCount: number }>;
/** Clear error */
clearError: () => void;
}
@@ -318,6 +321,47 @@ export const useAgentGroupsStore = create<AgentGroupsStore>()(
clearError: () => {
set({ error: null });
},
deleteGroup: async (groupName: string) => {
const { groups, selectedGroupName } = get();
const group = groups.find((g) => g.name === groupName);
if (!group) {
return { success: false, deletedCount: 0, failedCount: 0 };
}
// Get all session IDs from the group
const sessionIds = group.sessions.map((s) => s.id);
if (sessionIds.length === 0) {
return { success: true, deletedCount: 0, failedCount: 0 };
}
// Delete sessions using sessionStore.deleteSessions
// archiveWorktree: true - removes the git worktree
// deleteRemoteBranch: false - does not delete remote branch
const { deletedIds, failedIds } = await useSessionStore.getState().deleteSessions(
sessionIds,
{
archiveWorktree: true,
deleteRemoteBranch: false,
}
);
// If the deleted group was selected, clear selection
if (selectedGroupName === groupName) {
set({ selectedGroupName: null, selectedSessionId: null });
}
// Reload groups to reflect changes
await get().loadGroups();
return {
success: failedIds.length === 0,
deletedCount: deletedIds.length,
failedCount: failedIds.length,
};
},
}),
{ name: 'agent-groups-store' }
)