groove-dev 0.27.161 → 0.27.164
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/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/gateways/manager.js +1 -0
- package/node_modules/@groove-dev/daemon/src/index.js +3 -0
- package/node_modules/@groove-dev/daemon/src/model-lab.js +15 -0
- package/node_modules/@groove-dev/daemon/src/process.js +227 -105
- package/node_modules/@groove-dev/daemon/src/routes/teams.js +2 -0
- package/node_modules/@groove-dev/daemon/src/scheduler.js +1 -0
- package/node_modules/@groove-dev/daemon/src/tunnel-manager.js +4 -8
- package/node_modules/@groove-dev/gui/dist/assets/index-BJVNpGIp.css +1 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-CkCFf4Fl.js +1025 -0
- 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/App.jsx +2 -0
- package/node_modules/@groove-dev/gui/src/components/agents/diff-viewer.jsx +20 -12
- package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +5 -2
- package/node_modules/@groove-dev/gui/src/components/agents/workspace-mode.jsx +2 -19
- package/node_modules/@groove-dev/gui/src/components/fleet/fleet-agent-row.jsx +176 -0
- package/node_modules/@groove-dev/gui/src/components/fleet/fleet-content.jsx +135 -0
- package/node_modules/@groove-dev/gui/src/components/fleet/fleet-pane.jsx +105 -0
- package/node_modules/@groove-dev/gui/src/components/fleet/fleet-sidebar.jsx +216 -0
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +2 -1
- package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +4 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/ui-slice.js +71 -1
- package/node_modules/@groove-dev/gui/src/views/fleet.jsx +15 -0
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/gateways/manager.js +1 -0
- package/packages/daemon/src/index.js +3 -0
- package/packages/daemon/src/model-lab.js +15 -0
- package/packages/daemon/src/process.js +227 -105
- package/packages/daemon/src/routes/teams.js +2 -0
- package/packages/daemon/src/scheduler.js +1 -0
- package/packages/daemon/src/tunnel-manager.js +4 -8
- package/packages/gui/dist/assets/index-BJVNpGIp.css +1 -0
- package/packages/gui/dist/assets/index-CkCFf4Fl.js +1025 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/App.jsx +2 -0
- package/packages/gui/src/components/agents/diff-viewer.jsx +20 -12
- package/packages/gui/src/components/agents/spawn-wizard.jsx +5 -2
- package/packages/gui/src/components/agents/workspace-mode.jsx +2 -19
- package/packages/gui/src/components/fleet/fleet-agent-row.jsx +176 -0
- package/packages/gui/src/components/fleet/fleet-content.jsx +135 -0
- package/packages/gui/src/components/fleet/fleet-pane.jsx +105 -0
- package/packages/gui/src/components/fleet/fleet-sidebar.jsx +216 -0
- package/packages/gui/src/components/layout/activity-bar.jsx +2 -1
- package/packages/gui/src/stores/slices/agents-slice.js +4 -0
- package/packages/gui/src/stores/slices/ui-slice.js +71 -1
- package/packages/gui/src/views/fleet.jsx +15 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-DpRdb7o1.js +0 -1020
- package/node_modules/@groove-dev/gui/dist/assets/index-Dzofq3wS.css +0 -1
- package/packages/gui/dist/assets/index-DpRdb7o1.js +0 -1020
- package/packages/gui/dist/assets/index-Dzofq3wS.css +0 -1
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
import { useRef, useCallback, useMemo, useState } from 'react';
|
|
3
|
+
import { Search, X, ChevronRight, Plus, Trash2 } from 'lucide-react';
|
|
4
|
+
import { useGrooveStore } from '../../stores/groove';
|
|
5
|
+
import { cn } from '../../lib/cn';
|
|
6
|
+
import { FleetAgentRow } from './fleet-agent-row';
|
|
7
|
+
|
|
8
|
+
function teamStatusDot(agents) {
|
|
9
|
+
if (agents.some((a) => a.status === 'crashed')) return 'bg-danger';
|
|
10
|
+
if (agents.some((a) => a.status === 'running' || a.status === 'starting')) return 'bg-accent';
|
|
11
|
+
if (agents.some((a) => a.status === 'completed')) return 'bg-info';
|
|
12
|
+
return 'bg-text-4';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function FleetSidebar({ width }) {
|
|
16
|
+
const teams = useGrooveStore((s) => s.teams);
|
|
17
|
+
const agents = useGrooveStore((s) => s.agents);
|
|
18
|
+
const search = useGrooveStore((s) => s.fleetSearch);
|
|
19
|
+
const setSearch = useGrooveStore((s) => s.fleetSetSearch);
|
|
20
|
+
const collapsed = useGrooveStore((s) => s.fleetSidebarCollapsed);
|
|
21
|
+
const toggleCollapsed = useGrooveStore((s) => s.fleetToggleTeamCollapsed);
|
|
22
|
+
const setSidebarWidth = useGrooveStore((s) => s.fleetSetSidebarWidth);
|
|
23
|
+
const deleteTeam = useGrooveStore((s) => s.deleteTeam);
|
|
24
|
+
const openDetail = useGrooveStore((s) => s.openDetail);
|
|
25
|
+
|
|
26
|
+
const [confirmDeleteTeam, setConfirmDeleteTeam] = useState(null);
|
|
27
|
+
|
|
28
|
+
const dragging = useRef(false);
|
|
29
|
+
const startX = useRef(0);
|
|
30
|
+
const startW = useRef(0);
|
|
31
|
+
|
|
32
|
+
const onMouseDown = useCallback((e) => {
|
|
33
|
+
e.preventDefault();
|
|
34
|
+
dragging.current = true;
|
|
35
|
+
startX.current = e.clientX;
|
|
36
|
+
startW.current = width;
|
|
37
|
+
|
|
38
|
+
function onMouseMove(ev) {
|
|
39
|
+
if (!dragging.current) return;
|
|
40
|
+
const delta = ev.clientX - startX.current;
|
|
41
|
+
setSidebarWidth(startW.current + delta);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function onMouseUp() {
|
|
45
|
+
dragging.current = false;
|
|
46
|
+
document.removeEventListener('mousemove', onMouseMove);
|
|
47
|
+
document.removeEventListener('mouseup', onMouseUp);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
document.addEventListener('mousemove', onMouseMove);
|
|
51
|
+
document.addEventListener('mouseup', onMouseUp);
|
|
52
|
+
}, [width, setSidebarWidth]);
|
|
53
|
+
|
|
54
|
+
const agentsByTeam = useMemo(() => {
|
|
55
|
+
const map = {};
|
|
56
|
+
for (const a of agents) {
|
|
57
|
+
if (!a.teamId) continue;
|
|
58
|
+
if (!map[a.teamId]) map[a.teamId] = [];
|
|
59
|
+
map[a.teamId].push(a);
|
|
60
|
+
}
|
|
61
|
+
return map;
|
|
62
|
+
}, [agents]);
|
|
63
|
+
|
|
64
|
+
const lowerSearch = search.toLowerCase();
|
|
65
|
+
const filteredTeams = useMemo(() => {
|
|
66
|
+
if (!lowerSearch) return teams;
|
|
67
|
+
return teams.filter((t) => {
|
|
68
|
+
if (t.name?.toLowerCase().includes(lowerSearch)) return true;
|
|
69
|
+
const ta = agentsByTeam[t.id] || [];
|
|
70
|
+
return ta.some((a) =>
|
|
71
|
+
a.name?.toLowerCase().includes(lowerSearch) ||
|
|
72
|
+
a.role?.toLowerCase().includes(lowerSearch)
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
}, [teams, agentsByTeam, lowerSearch]);
|
|
76
|
+
|
|
77
|
+
const filteredAgentsForTeam = useCallback((teamId) => {
|
|
78
|
+
const ta = agentsByTeam[teamId] || [];
|
|
79
|
+
if (!lowerSearch) return ta;
|
|
80
|
+
return ta.filter((a) =>
|
|
81
|
+
a.name?.toLowerCase().includes(lowerSearch) ||
|
|
82
|
+
a.role?.toLowerCase().includes(lowerSearch)
|
|
83
|
+
);
|
|
84
|
+
}, [agentsByTeam, lowerSearch]);
|
|
85
|
+
|
|
86
|
+
function handleDeleteTeam(e, teamId) {
|
|
87
|
+
e.stopPropagation();
|
|
88
|
+
if (confirmDeleteTeam === teamId) {
|
|
89
|
+
deleteTeam(teamId);
|
|
90
|
+
setConfirmDeleteTeam(null);
|
|
91
|
+
} else {
|
|
92
|
+
setConfirmDeleteTeam(teamId);
|
|
93
|
+
setTimeout(() => setConfirmDeleteTeam(null), 3000);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function handleSpawnToTeam(e, teamId) {
|
|
98
|
+
e.stopPropagation();
|
|
99
|
+
openDetail({ type: 'spawn', presetTeamId: teamId });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div
|
|
104
|
+
className="flex-shrink-0 flex flex-col bg-surface-1 border-r border-border relative h-full"
|
|
105
|
+
style={{ width }}
|
|
106
|
+
>
|
|
107
|
+
{/* Search */}
|
|
108
|
+
<div className="px-2.5 pt-2.5 pb-2 flex-shrink-0">
|
|
109
|
+
<div className="relative">
|
|
110
|
+
<Search size={14} className="absolute left-2 top-1/2 -translate-y-1/2 text-text-4" />
|
|
111
|
+
<input
|
|
112
|
+
type="text"
|
|
113
|
+
value={search}
|
|
114
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
115
|
+
placeholder="Search agents..."
|
|
116
|
+
className="w-full h-7 pl-7 pr-7 text-xs bg-surface-3 rounded border border-border-subtle text-text-0 placeholder:text-text-4 focus:outline-none focus:border-text-4/40 font-sans"
|
|
117
|
+
/>
|
|
118
|
+
{search && (
|
|
119
|
+
<button
|
|
120
|
+
onClick={() => setSearch('')}
|
|
121
|
+
className="absolute right-1.5 top-1/2 -translate-y-1/2 p-0.5 rounded text-text-4 hover:text-text-1 cursor-pointer"
|
|
122
|
+
>
|
|
123
|
+
<X size={14} />
|
|
124
|
+
</button>
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
{/* Team list */}
|
|
130
|
+
<div className="flex-1 overflow-y-auto min-h-0 px-1">
|
|
131
|
+
{filteredTeams.map((team) => {
|
|
132
|
+
const teamAgents = filteredAgentsForTeam(team.id);
|
|
133
|
+
const allTeamAgents = agentsByTeam[team.id] || [];
|
|
134
|
+
const isCollapsed = collapsed[team.id];
|
|
135
|
+
const isConfirming = confirmDeleteTeam === team.id;
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<div key={team.id} className="mb-0.5">
|
|
139
|
+
{/* Team header */}
|
|
140
|
+
<div className={cn(
|
|
141
|
+
'w-full flex items-center gap-1 px-2 py-1.5 rounded-md hover:bg-surface-2 transition-colors group',
|
|
142
|
+
isConfirming && 'bg-danger/10 hover:bg-danger/20',
|
|
143
|
+
)}>
|
|
144
|
+
<button
|
|
145
|
+
onClick={() => toggleCollapsed(team.id)}
|
|
146
|
+
className="flex items-center gap-1.5 flex-1 min-w-0 cursor-pointer"
|
|
147
|
+
>
|
|
148
|
+
<ChevronRight
|
|
149
|
+
size={14}
|
|
150
|
+
className={cn(
|
|
151
|
+
'text-text-4 transition-transform flex-shrink-0',
|
|
152
|
+
!isCollapsed && 'rotate-90',
|
|
153
|
+
)}
|
|
154
|
+
/>
|
|
155
|
+
<span className={cn(
|
|
156
|
+
'text-xs font-medium font-sans truncate text-left',
|
|
157
|
+
isConfirming ? 'text-danger' : 'text-text-1',
|
|
158
|
+
)}>
|
|
159
|
+
{isConfirming ? 'Click again to delete' : team.name}
|
|
160
|
+
</span>
|
|
161
|
+
</button>
|
|
162
|
+
|
|
163
|
+
{/* Hover actions + meta — stacked in same space */}
|
|
164
|
+
<div className="flex items-center gap-0.5 flex-shrink-0">
|
|
165
|
+
<button
|
|
166
|
+
onClick={(e) => handleSpawnToTeam(e, team.id)}
|
|
167
|
+
className="opacity-0 group-hover:opacity-100 p-0.5 rounded text-text-4 hover:text-accent transition-opacity cursor-pointer"
|
|
168
|
+
title="Spawn agent to team"
|
|
169
|
+
>
|
|
170
|
+
<Plus size={14} />
|
|
171
|
+
</button>
|
|
172
|
+
<button
|
|
173
|
+
onClick={(e) => handleDeleteTeam(e, team.id)}
|
|
174
|
+
className={cn(
|
|
175
|
+
'opacity-0 group-hover:opacity-100 p-0.5 rounded transition-opacity cursor-pointer',
|
|
176
|
+
isConfirming ? 'text-danger' : 'text-text-4 hover:text-danger',
|
|
177
|
+
)}
|
|
178
|
+
title="Delete team"
|
|
179
|
+
>
|
|
180
|
+
<Trash2 size={12} />
|
|
181
|
+
</button>
|
|
182
|
+
<span className="group-hover:opacity-0 text-2xs text-text-4 font-mono transition-opacity">
|
|
183
|
+
{allTeamAgents.length}
|
|
184
|
+
</span>
|
|
185
|
+
<span className={cn('w-1.5 h-1.5 rounded-full flex-shrink-0', teamStatusDot(allTeamAgents))} />
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
{/* Agent rows */}
|
|
190
|
+
{!isCollapsed && teamAgents.length > 0 && (
|
|
191
|
+
<div className="ml-3 pl-1">
|
|
192
|
+
{teamAgents.map((agent) => (
|
|
193
|
+
<FleetAgentRow key={agent.id} agent={agent} />
|
|
194
|
+
))}
|
|
195
|
+
</div>
|
|
196
|
+
)}
|
|
197
|
+
</div>
|
|
198
|
+
);
|
|
199
|
+
})}
|
|
200
|
+
|
|
201
|
+
{filteredTeams.length === 0 && (
|
|
202
|
+
<div className="flex flex-col items-center justify-center py-8 text-center px-4">
|
|
203
|
+
<Search size={16} className="text-text-4 mb-2" />
|
|
204
|
+
<p className="text-xs text-text-3 font-sans">No matching agents</p>
|
|
205
|
+
</div>
|
|
206
|
+
)}
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
{/* Resize handle */}
|
|
210
|
+
<div
|
|
211
|
+
className="absolute right-0 top-0 bottom-0 w-1 cursor-col-resize hover:bg-accent/30 transition-colors z-10"
|
|
212
|
+
onMouseDown={onMouseDown}
|
|
213
|
+
/>
|
|
214
|
+
</div>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
-
import { Network, Code2, ChartSpline, Puzzle, Users, Box, FlaskConical, Newspaper, Settings, Globe, MessageCircle, BookOpen } from 'lucide-react';
|
|
2
|
+
import { Network, Code2, ChartSpline, Puzzle, Users, Box, FlaskConical, Newspaper, Settings, Globe, MessageCircle, BookOpen, LayoutList } from 'lucide-react';
|
|
3
3
|
import { cn } from '../../lib/cn';
|
|
4
4
|
import { Tooltip } from '../ui/tooltip';
|
|
5
5
|
import { useGrooveStore } from '../../stores/groove';
|
|
@@ -7,6 +7,7 @@ import { isElectron, getPlatform } from '../../lib/electron';
|
|
|
7
7
|
|
|
8
8
|
const BASE_NAV_ITEMS = [
|
|
9
9
|
{ id: 'agents', icon: Network, label: 'Agents' },
|
|
10
|
+
{ id: 'fleet', icon: LayoutList, label: 'Fleet' },
|
|
10
11
|
{ id: 'chat', icon: MessageCircle, label: 'Chat' },
|
|
11
12
|
{ id: 'editor', icon: Code2, label: 'Editor' },
|
|
12
13
|
{ id: 'dashboard', icon: ChartSpline, label: 'Dashboard' },
|
|
@@ -188,6 +188,10 @@ export const createAgentsSlice = (set, get) => ({
|
|
|
188
188
|
if (get().labAssistantAgentId === id) {
|
|
189
189
|
localStorage.setItem('groove:labAssistantAgentId', newAgent.id);
|
|
190
190
|
set({ labAssistantAgentId: newAgent.id });
|
|
191
|
+
} else if (get().activeView === 'fleet') {
|
|
192
|
+
const sel = get().fleetSelectedAgents;
|
|
193
|
+
const pane = sel[1] === id ? 1 : 0;
|
|
194
|
+
get().fleetSelectAgent(newAgent.id, pane);
|
|
191
195
|
} else {
|
|
192
196
|
get().selectAgent(newAgent.id);
|
|
193
197
|
}
|
|
@@ -25,6 +25,14 @@ export const createUiSlice = (set, get) => ({
|
|
|
25
25
|
// ── Toasts ────────────────────────────────────────────────
|
|
26
26
|
toasts: [],
|
|
27
27
|
|
|
28
|
+
// ── Fleet View ─────────────────────────────────────────────
|
|
29
|
+
fleetSelectedAgents: [null, null],
|
|
30
|
+
fleetSplitMode: false,
|
|
31
|
+
fleetSidebarWidth: Number(localStorage.getItem('groove:fleetSidebarWidth')) || 240,
|
|
32
|
+
fleetSidebarCollapsed: {},
|
|
33
|
+
fleetSearch: '',
|
|
34
|
+
fleetUnreadMap: {},
|
|
35
|
+
|
|
28
36
|
// ── Version / Auto-Update ──────────────────────────────────
|
|
29
37
|
version: null,
|
|
30
38
|
updateReady: null,
|
|
@@ -33,7 +41,34 @@ export const createUiSlice = (set, get) => ({
|
|
|
33
41
|
|
|
34
42
|
// ── Navigation ────────────────────────────────────────────
|
|
35
43
|
|
|
36
|
-
setActiveView(view) {
|
|
44
|
+
setActiveView(view) {
|
|
45
|
+
const prev = get().activeView;
|
|
46
|
+
const updates = { activeView: view };
|
|
47
|
+
if (prev === 'fleet' && view !== 'fleet') {
|
|
48
|
+
const sel = get().fleetSelectedAgents;
|
|
49
|
+
const primaryId = sel[0] || sel[1];
|
|
50
|
+
if (primaryId) {
|
|
51
|
+
const tid = get().activeTeamId;
|
|
52
|
+
const panel = { type: 'agent', agentId: primaryId };
|
|
53
|
+
updates.detailPanel = panel;
|
|
54
|
+
updates.teamDetailPanels = { ...get().teamDetailPanels, [tid]: panel };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (view === 'fleet' && prev !== 'fleet') {
|
|
58
|
+
const dp = get().detailPanel;
|
|
59
|
+
const sel = get().fleetSelectedAgents;
|
|
60
|
+
if (!sel[0] && !sel[1] && dp?.type === 'agent' && dp.agentId) {
|
|
61
|
+
updates.fleetSelectedAgents = [dp.agentId, null];
|
|
62
|
+
}
|
|
63
|
+
const tid = get().activeTeamId;
|
|
64
|
+
updates.detailPanel = null;
|
|
65
|
+
updates.teamDetailPanels = { ...get().teamDetailPanels, [tid]: null };
|
|
66
|
+
const allCollapsed = {};
|
|
67
|
+
for (const t of get().teams) allCollapsed[t.id] = true;
|
|
68
|
+
updates.fleetSidebarCollapsed = allCollapsed;
|
|
69
|
+
}
|
|
70
|
+
set(updates);
|
|
71
|
+
},
|
|
37
72
|
|
|
38
73
|
openDetail(descriptor) {
|
|
39
74
|
const tid = get().activeTeamId;
|
|
@@ -73,6 +108,41 @@ export const createUiSlice = (set, get) => ({
|
|
|
73
108
|
persistJSON('groove:expandedNodes', expanded);
|
|
74
109
|
},
|
|
75
110
|
|
|
111
|
+
// ── Fleet View ────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
fleetSelectAgent(agentId, pane = 0) {
|
|
114
|
+
const selected = [...get().fleetSelectedAgents];
|
|
115
|
+
selected[pane] = agentId;
|
|
116
|
+
const updates = { fleetSelectedAgents: selected };
|
|
117
|
+
if (pane === 1 && agentId !== null && !get().fleetSplitMode) {
|
|
118
|
+
updates.fleetSplitMode = true;
|
|
119
|
+
}
|
|
120
|
+
if (pane === 1 && agentId === null) {
|
|
121
|
+
updates.fleetSplitMode = false;
|
|
122
|
+
}
|
|
123
|
+
set(updates);
|
|
124
|
+
},
|
|
125
|
+
fleetToggleSplit() {
|
|
126
|
+
const next = !get().fleetSplitMode;
|
|
127
|
+
const selected = [...get().fleetSelectedAgents];
|
|
128
|
+
if (!next) selected[1] = null;
|
|
129
|
+
set({ fleetSplitMode: next, fleetSelectedAgents: selected });
|
|
130
|
+
},
|
|
131
|
+
fleetSetSidebarWidth(width) {
|
|
132
|
+
const w = Math.max(180, Math.min(400, width));
|
|
133
|
+
set({ fleetSidebarWidth: w });
|
|
134
|
+
localStorage.setItem('groove:fleetSidebarWidth', String(w));
|
|
135
|
+
},
|
|
136
|
+
fleetToggleTeamCollapsed(teamId) {
|
|
137
|
+
const collapsed = { ...get().fleetSidebarCollapsed };
|
|
138
|
+
collapsed[teamId] = !collapsed[teamId];
|
|
139
|
+
set({ fleetSidebarCollapsed: collapsed });
|
|
140
|
+
},
|
|
141
|
+
fleetSetSearch(text) { set({ fleetSearch: text }); },
|
|
142
|
+
fleetMarkRead(agentId) {
|
|
143
|
+
set((s) => ({ fleetUnreadMap: { ...s.fleetUnreadMap, [agentId]: Date.now() } }));
|
|
144
|
+
},
|
|
145
|
+
|
|
76
146
|
// ── Toasts ────────────────────────────────────────────────
|
|
77
147
|
|
|
78
148
|
addToast(type, message, detail, action, options = {}) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
import { FleetSidebar } from '../components/fleet/fleet-sidebar';
|
|
3
|
+
import { FleetContent } from '../components/fleet/fleet-content';
|
|
4
|
+
import { useGrooveStore } from '../stores/groove';
|
|
5
|
+
|
|
6
|
+
export default function FleetView() {
|
|
7
|
+
const sidebarWidth = useGrooveStore((s) => s.fleetSidebarWidth);
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<div className="flex h-full min-h-0">
|
|
11
|
+
<FleetSidebar width={sidebarWidth} />
|
|
12
|
+
<FleetContent />
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.164",
|
|
4
4
|
"description": "Open-source agent orchestration layer — the AI company OS. Local model agent engine (GGUF/Ollama/llama-server), HuggingFace model browser, MCP integrations (Slack, Gmail, Stripe, 15+), agent scheduling (cron), business roles (CMO, CFO, EA). GUI dashboard, multi-agent coordination, zero cold-start, infinite sessions. Works with Claude Code, Codex, Gemini CLI, Ollama, any local model.",
|
|
5
5
|
"license": "FSL-1.1-Apache-2.0",
|
|
6
6
|
"author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",
|
|
@@ -588,6 +588,9 @@ export class Daemon {
|
|
|
588
588
|
console.log(` ${resumableIds.size} agent-loop session(s) marked as resumable`);
|
|
589
589
|
}
|
|
590
590
|
|
|
591
|
+
// Restore pending phase 2 groups from disk
|
|
592
|
+
this.processes.loadPendingPhase2();
|
|
593
|
+
|
|
591
594
|
// Migrate old agents without teamId to default team
|
|
592
595
|
this.teams.migrateAgents();
|
|
593
596
|
|
|
@@ -8,6 +8,7 @@ import { homedir } from 'os';
|
|
|
8
8
|
import { spawn } from 'child_process';
|
|
9
9
|
import { LlamaServerManager } from './llama-server.js';
|
|
10
10
|
import { MLXServerManager } from './mlx-server.js';
|
|
11
|
+
import { OllamaProvider } from './providers/ollama.js';
|
|
11
12
|
const RUNTIME_TYPES = ['ollama', 'vllm', 'llama-cpp', 'mlx', 'tgi', 'openai-compatible'];
|
|
12
13
|
const DEFAULT_OLLAMA_ENDPOINT = 'http://localhost:11434';
|
|
13
14
|
const GLOBAL_GROOVE_DIR = resolve(homedir(), '.groove');
|
|
@@ -698,11 +699,13 @@ export class ModelLab {
|
|
|
698
699
|
|
|
699
700
|
listLocalModels() {
|
|
700
701
|
const models = [];
|
|
702
|
+
const seen = new Set();
|
|
701
703
|
|
|
702
704
|
// GGUF models from ModelManager
|
|
703
705
|
const mm = this.daemon.modelManager;
|
|
704
706
|
if (mm) {
|
|
705
707
|
for (const m of mm.getInstalled().filter((m) => m.exists)) {
|
|
708
|
+
seen.add(m.id);
|
|
706
709
|
models.push({ ...m, type: 'gguf', compatibleBackends: ['llama-cpp'] });
|
|
707
710
|
}
|
|
708
711
|
}
|
|
@@ -711,10 +714,22 @@ export class ModelLab {
|
|
|
711
714
|
try {
|
|
712
715
|
const hfModels = MLXServerManager.scanModels();
|
|
713
716
|
for (const m of hfModels) {
|
|
717
|
+
seen.add(m.id);
|
|
714
718
|
models.push(m);
|
|
715
719
|
}
|
|
716
720
|
} catch { /* scan may fail */ }
|
|
717
721
|
|
|
722
|
+
// Ollama installed models
|
|
723
|
+
try {
|
|
724
|
+
if (OllamaProvider.isInstalled()) {
|
|
725
|
+
for (const m of OllamaProvider.getInstalledModels()) {
|
|
726
|
+
if (seen.has(m.id)) continue;
|
|
727
|
+
seen.add(m.id);
|
|
728
|
+
models.push({ ...m, type: 'ollama', compatibleBackends: ['ollama'] });
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
} catch { /* ollama may not be available */ }
|
|
732
|
+
|
|
718
733
|
return models;
|
|
719
734
|
}
|
|
720
735
|
|