groove-dev 0.27.161 → 0.27.163
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/CLAUDE.md +0 -7
- 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/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/gui/dist/assets/index-BJVNpGIp.css +1 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-BlNh_lRS.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 +86 -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/process.js +227 -105
- package/packages/daemon/src/routes/teams.js +2 -0
- package/packages/daemon/src/scheduler.js +1 -0
- package/packages/gui/dist/assets/index-BJVNpGIp.css +1 -0
- package/packages/gui/dist/assets/index-BlNh_lRS.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 +86 -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.163",
|
|
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
|
|