groove-dev 0.27.115 → 0.27.116
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/TRAINING_DATA_v4.md +6 -3
- package/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/cli/src/commands/team.js +27 -12
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/api.js +3 -2
- package/node_modules/@groove-dev/daemon/src/process.js +254 -208
- package/node_modules/@groove-dev/daemon/src/teams.js +53 -24
- package/node_modules/@groove-dev/daemon/src/tunnel-manager.js +3 -2
- package/node_modules/@groove-dev/gui/dist/assets/{index-D4Q72afD.css → index-DdN9RVnC.css} +1 -1
- package/node_modules/@groove-dev/gui/dist/assets/{index-BKCiOUDb.js → index-fq--PD7_.js} +1724 -1724
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/components/teams/team-removal-dialog.jsx +156 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +15 -4
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +10 -19
- package/node_modules/@groove-dev/gui/src/views/teams.jsx +17 -41
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/cli/src/commands/team.js +27 -12
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/api.js +3 -2
- package/packages/daemon/src/process.js +254 -208
- package/packages/daemon/src/teams.js +53 -24
- package/packages/daemon/src/tunnel-manager.js +3 -2
- package/packages/gui/dist/assets/{index-D4Q72afD.css → index-DdN9RVnC.css} +1 -1
- package/packages/gui/dist/assets/{index-BKCiOUDb.js → index-fq--PD7_.js} +1724 -1724
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/teams/team-removal-dialog.jsx +156 -0
- package/packages/gui/src/stores/groove.js +15 -4
- package/packages/gui/src/views/agents.jsx +10 -19
- package/packages/gui/src/views/teams.jsx +17 -41
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
8
8
|
<title>Groove GUI</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-fq--PD7_.js"></script>
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/vendor-26L3JoZv.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/reactflow-DoBZjiHE.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/codemirror-CFF1Lrnz.js">
|
|
13
13
|
<link rel="modulepreload" crossorigin href="/assets/xterm--7_ns2zW.js">
|
|
14
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
14
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DdN9RVnC.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
17
17
|
<div id="root"></div>
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { Dialog, DialogContent } from '../ui/dialog';
|
|
4
|
+
import { Button } from '../ui/button';
|
|
5
|
+
import { Archive, Trash2, AlertTriangle } from 'lucide-react';
|
|
6
|
+
|
|
7
|
+
export function TeamRemovalDialog({ team, open, onOpenChange, onArchive, onDeletePermanently }) {
|
|
8
|
+
const [confirmName, setConfirmName] = useState('');
|
|
9
|
+
const [showConfirmInput, setShowConfirmInput] = useState(false);
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (!open) {
|
|
13
|
+
setConfirmName('');
|
|
14
|
+
setShowConfirmInput(false);
|
|
15
|
+
}
|
|
16
|
+
}, [open]);
|
|
17
|
+
|
|
18
|
+
const nameMatch = confirmName === (team?.name || '');
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
22
|
+
<DialogContent title={`Remove Team: ${team?.name || ''}`} description="Choose how to remove this team">
|
|
23
|
+
<div className="px-5 py-4 space-y-4">
|
|
24
|
+
<p className="text-sm text-text-1 font-sans">
|
|
25
|
+
What would you like to do with this team and its files?
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
{/* Archive option */}
|
|
29
|
+
<button
|
|
30
|
+
onClick={() => { onArchive(team?.id); onOpenChange(false); }}
|
|
31
|
+
className="w-full flex items-start gap-3 p-3.5 rounded-lg border border-border-subtle bg-surface-0 hover:border-accent/30 hover:bg-surface-2 transition-all cursor-pointer text-left group"
|
|
32
|
+
>
|
|
33
|
+
<div className="w-8 h-8 rounded-md bg-accent/10 flex items-center justify-center flex-shrink-0 group-hover:bg-accent/20 transition-colors">
|
|
34
|
+
<Archive size={16} className="text-accent" />
|
|
35
|
+
</div>
|
|
36
|
+
<div className="min-w-0 flex-1">
|
|
37
|
+
<div className="text-sm font-semibold text-text-0 font-sans">Archive</div>
|
|
38
|
+
<p className="text-xs text-text-3 font-sans mt-0.5">
|
|
39
|
+
Files are preserved. You can restore the team later.
|
|
40
|
+
</p>
|
|
41
|
+
</div>
|
|
42
|
+
</button>
|
|
43
|
+
|
|
44
|
+
{/* Delete Permanently option */}
|
|
45
|
+
<div className="rounded-lg border border-danger/20 bg-danger/5 overflow-hidden">
|
|
46
|
+
<button
|
|
47
|
+
onClick={() => setShowConfirmInput(true)}
|
|
48
|
+
className="w-full flex items-start gap-3 p-3.5 cursor-pointer text-left group"
|
|
49
|
+
>
|
|
50
|
+
<div className="w-8 h-8 rounded-md bg-danger/10 flex items-center justify-center flex-shrink-0 group-hover:bg-danger/20 transition-colors">
|
|
51
|
+
<Trash2 size={16} className="text-danger" />
|
|
52
|
+
</div>
|
|
53
|
+
<div className="min-w-0 flex-1">
|
|
54
|
+
<div className="text-sm font-semibold text-danger font-sans">Delete Permanently</div>
|
|
55
|
+
<p className="text-xs text-text-3 font-sans mt-0.5">
|
|
56
|
+
All files in this team will be permanently deleted.
|
|
57
|
+
</p>
|
|
58
|
+
</div>
|
|
59
|
+
</button>
|
|
60
|
+
|
|
61
|
+
{showConfirmInput && (
|
|
62
|
+
<div className="px-3.5 pb-3.5 space-y-2">
|
|
63
|
+
<div className="flex items-center gap-1.5 text-2xs text-warning font-sans">
|
|
64
|
+
<AlertTriangle size={11} />
|
|
65
|
+
<span>Type <span className="font-mono font-semibold text-text-0">{team?.name}</span> to confirm</span>
|
|
66
|
+
</div>
|
|
67
|
+
<input
|
|
68
|
+
type="text"
|
|
69
|
+
value={confirmName}
|
|
70
|
+
onChange={(e) => setConfirmName(e.target.value)}
|
|
71
|
+
placeholder={team?.name}
|
|
72
|
+
className="w-full h-8 px-3 text-xs bg-surface-0 border border-border-subtle rounded-md text-text-0 font-mono placeholder:text-text-4 focus:outline-none focus:ring-1 focus:ring-danger"
|
|
73
|
+
autoFocus
|
|
74
|
+
spellCheck={false}
|
|
75
|
+
onKeyDown={(e) => {
|
|
76
|
+
if (e.key === 'Enter' && nameMatch) {
|
|
77
|
+
onDeletePermanently(team?.id);
|
|
78
|
+
onOpenChange(false);
|
|
79
|
+
}
|
|
80
|
+
if (e.key === 'Escape') setShowConfirmInput(false);
|
|
81
|
+
}}
|
|
82
|
+
/>
|
|
83
|
+
<Button
|
|
84
|
+
variant="danger"
|
|
85
|
+
size="sm"
|
|
86
|
+
disabled={!nameMatch}
|
|
87
|
+
onClick={() => { onDeletePermanently(team?.id); onOpenChange(false); }}
|
|
88
|
+
className="w-full"
|
|
89
|
+
>
|
|
90
|
+
<Trash2 size={12} /> Delete Forever
|
|
91
|
+
</Button>
|
|
92
|
+
</div>
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div className="px-5 py-3 border-t border-border-subtle flex justify-end">
|
|
98
|
+
<Button variant="ghost" size="sm" onClick={() => onOpenChange(false)}>Cancel</Button>
|
|
99
|
+
</div>
|
|
100
|
+
</DialogContent>
|
|
101
|
+
</Dialog>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function PurgeConfirmDialog({ team, open, onOpenChange, onPurge }) {
|
|
106
|
+
const [confirmName, setConfirmName] = useState('');
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (!open) setConfirmName('');
|
|
110
|
+
}, [open]);
|
|
111
|
+
|
|
112
|
+
const displayName = team?.originalName || team?.name || '';
|
|
113
|
+
const nameMatch = confirmName === displayName;
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
117
|
+
<DialogContent title="Permanently Delete" description="Confirm permanent deletion">
|
|
118
|
+
<div className="px-5 py-4 space-y-3">
|
|
119
|
+
<p className="text-sm text-text-1 font-sans">
|
|
120
|
+
Permanently delete <span className="font-semibold text-text-0">{displayName}</span>?
|
|
121
|
+
</p>
|
|
122
|
+
<p className="text-xs text-danger font-sans">
|
|
123
|
+
This cannot be undone. All team files will be permanently removed.
|
|
124
|
+
</p>
|
|
125
|
+
<div className="space-y-2 pt-1">
|
|
126
|
+
<div className="flex items-center gap-1.5 text-2xs text-warning font-sans">
|
|
127
|
+
<AlertTriangle size={11} />
|
|
128
|
+
<span>Type <span className="font-mono font-semibold text-text-0">{displayName}</span> to confirm</span>
|
|
129
|
+
</div>
|
|
130
|
+
<input
|
|
131
|
+
type="text"
|
|
132
|
+
value={confirmName}
|
|
133
|
+
onChange={(e) => setConfirmName(e.target.value)}
|
|
134
|
+
placeholder={displayName}
|
|
135
|
+
className="w-full h-8 px-3 text-xs bg-surface-0 border border-border-subtle rounded-md text-text-0 font-mono placeholder:text-text-4 focus:outline-none focus:ring-1 focus:ring-danger"
|
|
136
|
+
autoFocus
|
|
137
|
+
spellCheck={false}
|
|
138
|
+
onKeyDown={(e) => {
|
|
139
|
+
if (e.key === 'Enter' && nameMatch) {
|
|
140
|
+
onPurge(team?.id);
|
|
141
|
+
onOpenChange(false);
|
|
142
|
+
}
|
|
143
|
+
}}
|
|
144
|
+
/>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
<div className="px-5 py-4 border-t border-border-subtle flex justify-end gap-2">
|
|
148
|
+
<Button variant="ghost" size="sm" onClick={() => onOpenChange(false)}>Cancel</Button>
|
|
149
|
+
<Button variant="danger" size="sm" disabled={!nameMatch} onClick={() => { onPurge(team?.id); onOpenChange(false); }}>
|
|
150
|
+
<Trash2 size={12} /> Delete Forever
|
|
151
|
+
</Button>
|
|
152
|
+
</div>
|
|
153
|
+
</DialogContent>
|
|
154
|
+
</Dialog>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
@@ -1164,21 +1164,32 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
1164
1164
|
}
|
|
1165
1165
|
},
|
|
1166
1166
|
|
|
1167
|
-
async
|
|
1167
|
+
async archiveTeam(id) {
|
|
1168
1168
|
const team = get().teams.find((t) => t.id === id);
|
|
1169
1169
|
try {
|
|
1170
1170
|
await api.delete(`/teams/${encodeURIComponent(id)}`);
|
|
1171
|
-
// WS team:deleted handler removes from array and switches activeTeamId.
|
|
1172
|
-
// Deleting the default team regenerates a fresh one server-side; the
|
|
1173
|
-
// team:created event arrives separately so the list stays populated.
|
|
1174
1171
|
const wiped = team?.isDefault ? 'wiped' : 'archived';
|
|
1175
1172
|
get().addToast('success', `Team "${team?.name}" ${wiped}`, wiped === 'archived' ? 'Files preserved — restore anytime from Archived Teams' : undefined);
|
|
1176
1173
|
get().fetchArchivedTeams();
|
|
1174
|
+
} catch (err) {
|
|
1175
|
+
get().addToast('error', 'Failed to archive team', err.message);
|
|
1176
|
+
}
|
|
1177
|
+
},
|
|
1178
|
+
|
|
1179
|
+
async deleteTeamPermanently(id) {
|
|
1180
|
+
const team = get().teams.find((t) => t.id === id);
|
|
1181
|
+
try {
|
|
1182
|
+
await api.delete(`/teams/${encodeURIComponent(id)}?permanent=true`);
|
|
1183
|
+
get().addToast('success', `Team "${team?.name}" permanently deleted`);
|
|
1177
1184
|
} catch (err) {
|
|
1178
1185
|
get().addToast('error', 'Failed to delete team', err.message);
|
|
1179
1186
|
}
|
|
1180
1187
|
},
|
|
1181
1188
|
|
|
1189
|
+
async deleteTeam(id) {
|
|
1190
|
+
return get().archiveTeam(id);
|
|
1191
|
+
},
|
|
1192
|
+
|
|
1182
1193
|
reorderTeams(fromIndex, toIndex) {
|
|
1183
1194
|
const teams = [...get().teams];
|
|
1184
1195
|
const [moved] = teams.splice(fromIndex, 1);
|
|
@@ -15,6 +15,7 @@ import { PreviewWorkspace } from '../components/preview/preview-workspace';
|
|
|
15
15
|
import { WorkspaceMode } from '../components/agents/workspace-mode';
|
|
16
16
|
import { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, ContextMenuSeparator } from '../components/ui/context-menu';
|
|
17
17
|
import { Dialog, DialogContent } from '../components/ui/dialog';
|
|
18
|
+
import { TeamRemovalDialog } from '../components/teams/team-removal-dialog';
|
|
18
19
|
import { Select, SelectTrigger, SelectContent, SelectItem } from '../components/ui/select';
|
|
19
20
|
import { ScrollArea } from '../components/ui/scroll-area';
|
|
20
21
|
import { Tooltip } from '../components/ui/tooltip';
|
|
@@ -84,7 +85,8 @@ export function TeamTabBar() {
|
|
|
84
85
|
const agents = useGrooveStore((s) => s.agents);
|
|
85
86
|
const switchTeam = useGrooveStore((s) => s.switchTeam);
|
|
86
87
|
const createTeam = useGrooveStore((s) => s.createTeam);
|
|
87
|
-
const
|
|
88
|
+
const archiveTeam = useGrooveStore((s) => s.archiveTeam);
|
|
89
|
+
const deleteTeamPermanently = useGrooveStore((s) => s.deleteTeamPermanently);
|
|
88
90
|
const renameTeam = useGrooveStore((s) => s.renameTeam);
|
|
89
91
|
const cloneTeam = useGrooveStore((s) => s.cloneTeam);
|
|
90
92
|
const reorderTeams = useGrooveStore((s) => s.reorderTeams);
|
|
@@ -305,24 +307,13 @@ export function TeamTabBar() {
|
|
|
305
307
|
</button>
|
|
306
308
|
)}
|
|
307
309
|
|
|
308
|
-
<
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
All files will be preserved and can be restored later from the Archived Teams section.
|
|
316
|
-
</p>
|
|
317
|
-
</div>
|
|
318
|
-
<div className="px-5 py-4 border-t border-border-subtle flex justify-end gap-2">
|
|
319
|
-
<Button variant="ghost" size="sm" onClick={() => setArchiveConfirm(null)}>Cancel</Button>
|
|
320
|
-
<Button variant="danger" size="sm" onClick={() => { deleteTeam(archiveConfirm.id); setArchiveConfirm(null); }}>
|
|
321
|
-
<Archive size={12} /> Archive
|
|
322
|
-
</Button>
|
|
323
|
-
</div>
|
|
324
|
-
</DialogContent>
|
|
325
|
-
</Dialog>
|
|
310
|
+
<TeamRemovalDialog
|
|
311
|
+
team={archiveConfirm}
|
|
312
|
+
open={!!archiveConfirm}
|
|
313
|
+
onOpenChange={(open) => !open && setArchiveConfirm(null)}
|
|
314
|
+
onArchive={archiveTeam}
|
|
315
|
+
onDeletePermanently={deleteTeamPermanently}
|
|
316
|
+
/>
|
|
326
317
|
</div>
|
|
327
318
|
);
|
|
328
319
|
}
|
|
@@ -5,7 +5,6 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from '../components/ui/tabs'
|
|
|
5
5
|
import { Button } from '../components/ui/button';
|
|
6
6
|
import { Badge } from '../components/ui/badge';
|
|
7
7
|
import { StatusDot } from '../components/ui/status-dot';
|
|
8
|
-
import { Dialog, DialogContent } from '../components/ui/dialog';
|
|
9
8
|
import { api } from '../lib/api';
|
|
10
9
|
import { useToast } from '../lib/hooks/use-toast';
|
|
11
10
|
import { fmtNum, fmtDollar, timeAgo, fmtUptime } from '../lib/format';
|
|
@@ -15,13 +14,15 @@ import {
|
|
|
15
14
|
Users, Folder, Cpu, Trash2, Play, Pause, LayoutDashboard, ListChecks, Calendar,
|
|
16
15
|
Archive, RotateCcw, ChevronRight,
|
|
17
16
|
} from 'lucide-react';
|
|
17
|
+
import { TeamRemovalDialog, PurgeConfirmDialog } from '../components/teams/team-removal-dialog';
|
|
18
18
|
|
|
19
19
|
// ── Team Dashboard ────────────────────────────────────────────
|
|
20
20
|
function TeamsDashboard() {
|
|
21
21
|
const teams = useGrooveStore((s) => s.teams);
|
|
22
22
|
const agents = useGrooveStore((s) => s.agents);
|
|
23
23
|
const activeTeamId = useGrooveStore((s) => s.activeTeamId);
|
|
24
|
-
const
|
|
24
|
+
const archiveTeam = useGrooveStore((s) => s.archiveTeam);
|
|
25
|
+
const deleteTeamPermanently = useGrooveStore((s) => s.deleteTeamPermanently);
|
|
25
26
|
const addToast = useGrooveStore((s) => s.addToast);
|
|
26
27
|
const archivedTeams = useGrooveStore((s) => s.archivedTeams);
|
|
27
28
|
const fetchArchivedTeams = useGrooveStore((s) => s.fetchArchivedTeams);
|
|
@@ -82,7 +83,7 @@ function TeamsDashboard() {
|
|
|
82
83
|
</div>
|
|
83
84
|
<button
|
|
84
85
|
onClick={() => {
|
|
85
|
-
if (teamAgents.some((a) => a.status === 'running')) {
|
|
86
|
+
if (teamAgents.some((a) => a.status === 'running' || a.status === 'starting')) {
|
|
86
87
|
addToast('error', 'Stop running agents first');
|
|
87
88
|
return;
|
|
88
89
|
}
|
|
@@ -176,45 +177,20 @@ function TeamsDashboard() {
|
|
|
176
177
|
</div>
|
|
177
178
|
)}
|
|
178
179
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
<p className="text-xs text-text-2 font-sans">
|
|
187
|
-
All files will be preserved and can be restored later from the Archived Teams section.
|
|
188
|
-
</p>
|
|
189
|
-
</div>
|
|
190
|
-
<div className="px-5 py-4 border-t border-border-subtle flex justify-end gap-2">
|
|
191
|
-
<Button variant="ghost" size="sm" onClick={() => setArchiveConfirm(null)}>Cancel</Button>
|
|
192
|
-
<Button variant="danger" size="sm" onClick={() => { deleteTeam(archiveConfirm.id); setArchiveConfirm(null); }}>
|
|
193
|
-
<Archive size={12} /> Archive
|
|
194
|
-
</Button>
|
|
195
|
-
</div>
|
|
196
|
-
</DialogContent>
|
|
197
|
-
</Dialog>
|
|
180
|
+
<TeamRemovalDialog
|
|
181
|
+
team={archiveConfirm}
|
|
182
|
+
open={!!archiveConfirm}
|
|
183
|
+
onOpenChange={(open) => !open && setArchiveConfirm(null)}
|
|
184
|
+
onArchive={archiveTeam}
|
|
185
|
+
onDeletePermanently={deleteTeamPermanently}
|
|
186
|
+
/>
|
|
198
187
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
</p>
|
|
206
|
-
<p className="text-xs text-danger font-sans">
|
|
207
|
-
This cannot be undone. All team files will be permanently removed.
|
|
208
|
-
</p>
|
|
209
|
-
</div>
|
|
210
|
-
<div className="px-5 py-4 border-t border-border-subtle flex justify-end gap-2">
|
|
211
|
-
<Button variant="ghost" size="sm" onClick={() => setPurgeConfirm(null)}>Cancel</Button>
|
|
212
|
-
<Button variant="danger" size="sm" onClick={() => { purgeTeam(purgeConfirm.id); setPurgeConfirm(null); }}>
|
|
213
|
-
<Trash2 size={12} /> Delete Forever
|
|
214
|
-
</Button>
|
|
215
|
-
</div>
|
|
216
|
-
</DialogContent>
|
|
217
|
-
</Dialog>
|
|
188
|
+
<PurgeConfirmDialog
|
|
189
|
+
team={purgeConfirm}
|
|
190
|
+
open={!!purgeConfirm}
|
|
191
|
+
onOpenChange={(open) => !open && setPurgeConfirm(null)}
|
|
192
|
+
onPurge={purgeTeam}
|
|
193
|
+
/>
|
|
218
194
|
</div>
|
|
219
195
|
);
|
|
220
196
|
}
|