From bcd75feb45bfaeda97149415b65d98e6e2f70956 Mon Sep 17 00:00:00 2001 From: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 Date: Mon, 8 Jun 2026 17:54:04 -0400 Subject: [PATCH] =?UTF-8?q?feat(desktop):=20wire=20directory-backed=20team?= =?UTF-8?q?=20UI=20=E2=80=94=20install,=20sync,=20reveal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Connects the new backend team directory commands to the frontend: - Add sourceDir, isSymlink, symlinkTarget, version to AgentTeam type and tauriTeams.ts RawTeam mapping - Add pickTeamDirectory, installTeamFromDirectory, syncTeamDirectory API wrappers in tauriTeams.ts - TeamsSection: "Install from directory" button, symlink link icon with tooltip, version badge, "Sync from directory" and "Reveal in Finder" menu items for directory-backed teams - useTeamActions: handleInstallFromDirectory (pick + install + symlink), handleSyncTeam (sync + summary notification), handleRevealInFinder - AgentsView: pass new callbacks to TeamsSection --- desktop/src/features/agents/ui/AgentsView.tsx | 3 + .../src/features/agents/ui/TeamsSection.tsx | 66 +++++++++++++++++-- .../src/features/agents/ui/useTeamActions.ts | 64 ++++++++++++++++++ desktop/src/shared/api/tauriTeams.ts | 35 ++++++++++ desktop/src/shared/api/types.ts | 8 +++ 5 files changed, 171 insertions(+), 5 deletions(-) diff --git a/desktop/src/features/agents/ui/AgentsView.tsx b/desktop/src/features/agents/ui/AgentsView.tsx index ff50219caa..5786a01833 100644 --- a/desktop/src/features/agents/ui/AgentsView.tsx +++ b/desktop/src/features/agents/ui/AgentsView.tsx @@ -150,6 +150,9 @@ export function AgentsView() { onEdit={teamActions.openEditDialog} onExport={teamActions.handleExportTeam} onImportFile={teamActions.handleImportFile} + onInstallFromDirectory={teamActions.handleInstallFromDirectory} + onSync={teamActions.handleSyncTeam} + onRevealInFinder={teamActions.handleRevealInFinder} onAddToChannel={teamActions.setTeamToAddToChannel} personas={personas.libraryPersonas} teams={teamActions.teams} diff --git a/desktop/src/features/agents/ui/TeamsSection.tsx b/desktop/src/features/agents/ui/TeamsSection.tsx index 6ec719ec75..bcbcd7e104 100644 --- a/desktop/src/features/agents/ui/TeamsSection.tsx +++ b/desktop/src/features/agents/ui/TeamsSection.tsx @@ -2,7 +2,10 @@ import { CopyPlus, Download, Ellipsis, + FolderOpen, + FolderSync, Info, + Link, Pencil, Rocket, Trash2, @@ -41,6 +44,9 @@ type TeamsSectionProps = { onDelete: (team: AgentTeam) => void; onAddToChannel: (team: AgentTeam) => void; onImportFile: (fileBytes: number[], fileName: string) => void; + onInstallFromDirectory: () => void; + onSync: (team: AgentTeam) => void; + onRevealInFinder: (team: AgentTeam) => void; }; export function TeamsSection({ @@ -56,6 +62,9 @@ export function TeamsSection({ onDelete, onAddToChannel, onImportFile, + onInstallFromDirectory, + onSync, + onRevealInFinder, }: TeamsSectionProps) { const { fileInputRef, @@ -93,11 +102,21 @@ export function TeamsSection({ ref={fileInputRef} type="file" /> - +
+ + +
{isLoading ? ( @@ -138,6 +157,25 @@ export function TeamsSection({

{team.name}

+ {team.isSymlink ? ( + + + + + + + +

+ Linked from {team.symlinkTarget ?? team.sourceDir} +

+
+
+ ) : null} + {team.version ? ( + + v{team.version} + + ) : null} {team.description ? ( @@ -221,6 +259,24 @@ export function TeamsSection({ Export + {team.sourceDir ? ( + <> + + onSync(team)} + > + + Sync from directory + + onRevealInFinder(team)} + > + + Reveal in Finder + + + ) : null} 0 && + `${result.personas_added.length} added`, + result.personas_updated.length > 0 && + `${result.personas_updated.length} updated`, + result.personas_removed.length > 0 && + `${result.personas_removed.length} removed`, + ].filter(Boolean); + const summary = + changes.length > 0 ? changes.join(", ") : "already up to date"; + actions.setActionNoticeMessage(`Synced "${team.name}": ${summary}.`); + await Promise.all([ + queryClient.invalidateQueries({ queryKey: teamsQueryKey }), + queryClient.invalidateQueries({ queryKey: personasQueryKey }), + queryClient.invalidateQueries({ queryKey: managedAgentsQueryKey }), + ]); + } catch (err) { + actions.setActionErrorMessage( + err instanceof Error ? err.message : "Failed to sync team directory.", + ); + } + } + + function handleRevealInFinder(team: AgentTeam) { + if (!team.sourceDir) return; + void revealItemInDir(team.sourceDir); + } + async function handleEditDialogImportUpdateFile( teamId: string, fileBytes: number[], @@ -492,6 +553,9 @@ export function useTeamActions( handleTeamDeployed, handleExportTeam, handleImportFile, + handleInstallFromDirectory, + handleSyncTeam, + handleRevealInFinder, handleEditDialogImportUpdateFile, handleTeamImportComplete, handleTeamImportUpdateApply, diff --git a/desktop/src/shared/api/tauriTeams.ts b/desktop/src/shared/api/tauriTeams.ts index ddb2617d96..166078b99f 100644 --- a/desktop/src/shared/api/tauriTeams.ts +++ b/desktop/src/shared/api/tauriTeams.ts @@ -11,6 +11,10 @@ type RawTeam = { description: string | null; persona_ids: string[]; is_builtin?: boolean; + source_dir?: string | null; + is_symlink?: boolean; + symlink_target?: string | null; + version?: string | null; created_at: string; updated_at: string; }; @@ -22,6 +26,10 @@ function fromRawTeam(team: RawTeam): AgentTeam { description: team.description, personaIds: team.persona_ids, isBuiltin: team.is_builtin ?? false, + sourceDir: team.source_dir ?? null, + isSymlink: team.is_symlink ?? false, + symlinkTarget: team.symlink_target ?? null, + version: team.version ?? null, createdAt: team.created_at, updatedAt: team.updated_at, }; @@ -83,3 +91,30 @@ export async function parseTeamFile( fileName, }); } + +export type SyncResult = { + personas_added: string[]; + personas_removed: string[]; + personas_updated: string[]; + metadata_changed: boolean; +}; + +export async function pickTeamDirectory(): Promise { + return invokeTauri("pick_team_directory"); +} + +export async function installTeamFromDirectory( + path: string, + symlink?: boolean, +): Promise { + return fromRawTeam( + await invokeTauri("install_team_from_directory", { + path, + symlink: symlink ?? false, + }), + ); +} + +export async function syncTeamDirectory(teamId: string): Promise { + return invokeTauri("sync_team_directory", { teamId }); +} diff --git a/desktop/src/shared/api/types.ts b/desktop/src/shared/api/types.ts index d37367f21c..ea01f03100 100644 --- a/desktop/src/shared/api/types.ts +++ b/desktop/src/shared/api/types.ts @@ -501,6 +501,14 @@ export type AgentTeam = { description: string | null; personaIds: string[]; isBuiltin: boolean; + /** Absolute path to the team's backing directory (if directory-backed). */ + sourceDir: string | null; + /** Whether sourceDir is a symlink to an external directory. */ + isSymlink: boolean; + /** Resolved symlink target path (for display). Only set when isSymlink is true. */ + symlinkTarget: string | null; + /** Version from the team's plugin.json manifest. */ + version: string | null; createdAt: string; updatedAt: string; };