groove-dev 0.27.131 → 0.27.134
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/AGENT_ORCHESTRATION.md +375 -0
- package/moe-training/shared/envelope-schema.js +1 -1
- 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/index.js +3 -1
- package/node_modules/@groove-dev/daemon/src/introducer.js +48 -4
- package/node_modules/@groove-dev/daemon/src/llama-server.js +4 -4
- package/node_modules/@groove-dev/daemon/src/model-lab.js +8 -0
- package/node_modules/@groove-dev/daemon/src/preview.js +85 -58
- package/node_modules/@groove-dev/daemon/src/process.js +9 -0
- package/node_modules/@groove-dev/daemon/src/terminal-pty.js +24 -14
- package/node_modules/@groove-dev/daemon/src/validate.js +0 -4
- package/{packages/gui/dist/assets/codemirror-CFF1Lrnz.js → node_modules/@groove-dev/gui/dist/assets/codemirror-DRQdprYi.js} +11 -11
- package/node_modules/@groove-dev/gui/dist/assets/index-BgQL4bNl.css +1 -0
- package/{packages/gui/dist/assets/index-BiB9oY9U.js → node_modules/@groove-dev/gui/dist/assets/index-Dozp69tK.js} +1721 -1721
- package/node_modules/@groove-dev/gui/dist/index.html +3 -3
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/app.css +6 -6
- package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +12 -1
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +15 -5
- package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +6 -6
- package/node_modules/@groove-dev/gui/src/components/agents/workspace-mode.jsx +11 -9
- package/node_modules/@groove-dev/gui/src/components/editor/code-editor.jsx +26 -3
- package/node_modules/@groove-dev/gui/src/components/editor/file-tree.jsx +6 -6
- package/node_modules/@groove-dev/gui/src/components/editor/terminal.jsx +20 -8
- package/node_modules/@groove-dev/gui/src/components/lab/chat-playground.jsx +10 -1
- package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +4 -4
- package/node_modules/@groove-dev/gui/src/components/lab/system-prompt-editor.jsx +17 -3
- package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +2 -4
- package/node_modules/@groove-dev/gui/src/components/preview/preview-toolbar.jsx +8 -6
- package/node_modules/@groove-dev/gui/src/stores/groove.js +82 -15
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +82 -74
- package/node_modules/@groove-dev/gui/src/views/editor.jsx +11 -9
- package/node_modules/moe-training/shared/envelope-schema.js +1 -1
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/index.js +3 -1
- package/packages/daemon/src/introducer.js +48 -4
- package/packages/daemon/src/llama-server.js +4 -4
- package/packages/daemon/src/model-lab.js +8 -0
- package/packages/daemon/src/preview.js +85 -58
- package/packages/daemon/src/process.js +9 -0
- package/packages/daemon/src/terminal-pty.js +24 -14
- package/packages/daemon/src/validate.js +0 -4
- package/{node_modules/@groove-dev/gui/dist/assets/codemirror-CFF1Lrnz.js → packages/gui/dist/assets/codemirror-DRQdprYi.js} +11 -11
- package/packages/gui/dist/assets/index-BgQL4bNl.css +1 -0
- package/{node_modules/@groove-dev/gui/dist/assets/index-BiB9oY9U.js → packages/gui/dist/assets/index-Dozp69tK.js} +1721 -1721
- package/packages/gui/dist/index.html +3 -3
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/app.css +6 -6
- package/packages/gui/src/components/agents/agent-chat.jsx +12 -1
- package/packages/gui/src/components/agents/agent-feed.jsx +15 -5
- package/packages/gui/src/components/agents/agent-file-tree.jsx +6 -6
- package/packages/gui/src/components/agents/workspace-mode.jsx +11 -9
- package/packages/gui/src/components/editor/code-editor.jsx +26 -3
- package/packages/gui/src/components/editor/file-tree.jsx +6 -6
- package/packages/gui/src/components/editor/terminal.jsx +20 -8
- package/packages/gui/src/components/lab/chat-playground.jsx +10 -1
- package/packages/gui/src/components/lab/lab-assistant.jsx +4 -4
- package/packages/gui/src/components/lab/system-prompt-editor.jsx +17 -3
- package/packages/gui/src/components/layout/terminal-panel.jsx +2 -4
- package/packages/gui/src/components/preview/preview-toolbar.jsx +8 -6
- package/packages/gui/src/stores/groove.js +82 -15
- package/packages/gui/src/views/agents.jsx +82 -74
- package/packages/gui/src/views/editor.jsx +11 -9
- package/CENTRAL_COMMAND_REBUILD.md +0 -689
- package/MERKLE_TREE_ARCHITECTURE.md +0 -354
- package/node_modules/@groove-dev/gui/dist/assets/index-CeyDFVub.css +0 -1
- package/packages/gui/dist/assets/index-CeyDFVub.css +0 -1
|
@@ -70,6 +70,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
70
70
|
showPreviewInAgents: false,
|
|
71
71
|
previewChat: [],
|
|
72
72
|
previewIterating: false,
|
|
73
|
+
teamPreviews: {}, // teamId -> { url, kind, active } — survives team switches
|
|
73
74
|
|
|
74
75
|
// ── Team Launch Config (set during planner spawn, cascades to team) ──
|
|
75
76
|
teamLaunchConfig: null, // { provider, model, reasoningEffort, temperature, verbosity, mode }
|
|
@@ -224,9 +225,9 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
224
225
|
labLlamaInstalled: null,
|
|
225
226
|
labLaunchPhase: null,
|
|
226
227
|
labLaunchError: null,
|
|
227
|
-
labAssistantAgentId: null,
|
|
228
|
+
labAssistantAgentId: localStorage.getItem('groove:labAssistantAgentId') || null,
|
|
228
229
|
labAssistantMode: false,
|
|
229
|
-
labAssistantBackend: null,
|
|
230
|
+
labAssistantBackend: localStorage.getItem('groove:labAssistantBackend') || null,
|
|
230
231
|
|
|
231
232
|
// ── Onboarding ────────────────────────────────────────────
|
|
232
233
|
onboardingComplete: localStorage.getItem('groove:onboardingComplete') === 'true',
|
|
@@ -553,15 +554,20 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
553
554
|
get().addToast('info', `QC agent ${msg.name} auto-spawned`, 'Auditing phase 1 work');
|
|
554
555
|
break;
|
|
555
556
|
|
|
556
|
-
case 'preview:ready':
|
|
557
|
+
case 'preview:ready': {
|
|
558
|
+
const proxyUrl = msg.teamId ? `/api/preview/${msg.teamId}/proxy/` : msg.url;
|
|
559
|
+
set((s) => ({
|
|
560
|
+
teamPreviews: { ...s.teamPreviews, [msg.teamId]: { url: proxyUrl, kind: msg.kind, active: true } },
|
|
561
|
+
}));
|
|
557
562
|
get().addToast(
|
|
558
563
|
'success',
|
|
559
564
|
'Project ready to preview',
|
|
560
565
|
msg.url,
|
|
561
|
-
{ label: 'Open Preview', onClick: () => get().openPreview(
|
|
566
|
+
{ label: 'Open Preview', onClick: () => get().openPreview(proxyUrl, msg.teamId, msg.kind) },
|
|
562
567
|
{ persistent: true },
|
|
563
568
|
);
|
|
564
569
|
break;
|
|
570
|
+
}
|
|
565
571
|
|
|
566
572
|
case 'preview:failed': {
|
|
567
573
|
const failKind = msg.kind || '';
|
|
@@ -576,9 +582,11 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
576
582
|
}
|
|
577
583
|
|
|
578
584
|
case 'preview:stopped': {
|
|
579
|
-
const
|
|
580
|
-
if (
|
|
581
|
-
set(
|
|
585
|
+
const tp = get().teamPreviews[msg.teamId];
|
|
586
|
+
if (tp) {
|
|
587
|
+
set((s) => ({
|
|
588
|
+
teamPreviews: { ...s.teamPreviews, [msg.teamId]: { ...tp, active: false } },
|
|
589
|
+
}));
|
|
582
590
|
}
|
|
583
591
|
break;
|
|
584
592
|
}
|
|
@@ -1235,11 +1243,15 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
1235
1243
|
},
|
|
1236
1244
|
|
|
1237
1245
|
switchTeam(id) {
|
|
1238
|
-
const { activeTeamId, detailPanel, teamDetailPanels } = get();
|
|
1246
|
+
const { activeTeamId, detailPanel, teamDetailPanels, teamPreviews } = get();
|
|
1239
1247
|
const updated = { ...teamDetailPanels };
|
|
1240
1248
|
if (activeTeamId) updated[activeTeamId] = detailPanel;
|
|
1241
1249
|
const restored = updated[id] || null;
|
|
1242
|
-
|
|
1250
|
+
const tp = teamPreviews[id];
|
|
1251
|
+
const previewUpdate = tp
|
|
1252
|
+
? { previewState: { url: tp.url, teamId: id, kind: tp.kind, deviceSize: 'desktop', screenshotMode: false } }
|
|
1253
|
+
: {};
|
|
1254
|
+
set({ activeTeamId: id, detailPanel: restored, teamDetailPanels: updated, ...previewUpdate });
|
|
1243
1255
|
localStorage.setItem('groove:activeTeamId', id);
|
|
1244
1256
|
},
|
|
1245
1257
|
|
|
@@ -1421,24 +1433,60 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
1421
1433
|
const data = await api.get('/preview');
|
|
1422
1434
|
const previews = data.previews || [];
|
|
1423
1435
|
if (previews.length > 0) {
|
|
1424
|
-
const
|
|
1425
|
-
|
|
1426
|
-
|
|
1436
|
+
const updates = {};
|
|
1437
|
+
for (const p of previews) {
|
|
1438
|
+
updates[p.teamId] = { url: `/api/preview/${p.teamId}/proxy/`, kind: p.kind, active: true };
|
|
1439
|
+
}
|
|
1440
|
+
const most = previews.sort((a, b) => (b.startedAt || 0) - (a.startedAt || 0))[0];
|
|
1441
|
+
set((s) => ({
|
|
1442
|
+
teamPreviews: { ...s.teamPreviews, ...updates },
|
|
1443
|
+
previewState: { url: `/api/preview/${most.teamId}/proxy/`, teamId: most.teamId, kind: most.kind, deviceSize: 'desktop', screenshotMode: false },
|
|
1427
1444
|
showPreviewInAgents: true,
|
|
1428
|
-
});
|
|
1445
|
+
}));
|
|
1429
1446
|
}
|
|
1430
1447
|
} catch {}
|
|
1431
1448
|
},
|
|
1432
1449
|
|
|
1433
1450
|
openPreview(url, teamId, kind) {
|
|
1434
|
-
set(
|
|
1451
|
+
set((s) => ({
|
|
1452
|
+
previewState: { url, teamId, kind, deviceSize: 'desktop', screenshotMode: false },
|
|
1453
|
+
teamPreviews: { ...s.teamPreviews, [teamId]: { url, kind, active: true } },
|
|
1454
|
+
previewChat: [],
|
|
1455
|
+
showPreviewInAgents: true,
|
|
1456
|
+
}));
|
|
1435
1457
|
},
|
|
1436
1458
|
closePreview() {
|
|
1459
|
+
set({ showPreviewInAgents: false });
|
|
1460
|
+
},
|
|
1461
|
+
stopPreview() {
|
|
1437
1462
|
const { previewState } = get();
|
|
1438
1463
|
if (previewState.teamId) {
|
|
1439
1464
|
api.delete(`/preview/${previewState.teamId}`).catch(() => {});
|
|
1465
|
+
set((s) => ({
|
|
1466
|
+
teamPreviews: {
|
|
1467
|
+
...s.teamPreviews,
|
|
1468
|
+
[previewState.teamId]: { ...s.teamPreviews[previewState.teamId], active: false },
|
|
1469
|
+
},
|
|
1470
|
+
showPreviewInAgents: false,
|
|
1471
|
+
}));
|
|
1472
|
+
}
|
|
1473
|
+
},
|
|
1474
|
+
async relaunchPreview(teamId) {
|
|
1475
|
+
try {
|
|
1476
|
+
const result = await api.post(`/preview/${teamId}/launch`);
|
|
1477
|
+
if (result.launched) {
|
|
1478
|
+
const proxyUrl = `/api/preview/${teamId}/proxy/`;
|
|
1479
|
+
set((s) => ({
|
|
1480
|
+
previewState: { url: proxyUrl, teamId, kind: result.kind, deviceSize: 'desktop', screenshotMode: false },
|
|
1481
|
+
teamPreviews: { ...s.teamPreviews, [teamId]: { url: proxyUrl, kind: result.kind, active: true } },
|
|
1482
|
+
showPreviewInAgents: true,
|
|
1483
|
+
}));
|
|
1484
|
+
} else {
|
|
1485
|
+
get().addToast('warning', 'Preview could not launch', result.reason ? String(result.reason).slice(0, 200) : 'Build or server failed');
|
|
1486
|
+
}
|
|
1487
|
+
} catch (err) {
|
|
1488
|
+
get().addToast('error', 'Failed to launch preview', err.message);
|
|
1440
1489
|
}
|
|
1441
|
-
set({ previewState: { url: null, teamId: null, kind: null, deviceSize: 'desktop', screenshotMode: false }, previewChat: [], previewIterating: false, showPreviewInAgents: false, activeView: 'agents' });
|
|
1442
1490
|
},
|
|
1443
1491
|
togglePreviewInAgents() {
|
|
1444
1492
|
set((s) => ({ showPreviewInAgents: !s.showPreviewInAgents }));
|
|
@@ -3500,6 +3548,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
3500
3548
|
const decoder = new TextDecoder();
|
|
3501
3549
|
let buffer = '';
|
|
3502
3550
|
let fullContent = '';
|
|
3551
|
+
let fullReasoning = '';
|
|
3503
3552
|
|
|
3504
3553
|
while (true) {
|
|
3505
3554
|
const { done, value } = await reader.read();
|
|
@@ -3514,6 +3563,20 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
3514
3563
|
if (payload === '[DONE]') continue;
|
|
3515
3564
|
try {
|
|
3516
3565
|
const chunk = JSON.parse(payload);
|
|
3566
|
+
if (chunk.type === 'reasoning' && chunk.content) {
|
|
3567
|
+
if (!firstTokenTime) firstTokenTime = performance.now();
|
|
3568
|
+
tokenCount++;
|
|
3569
|
+
fullReasoning += chunk.content;
|
|
3570
|
+
set((s) => {
|
|
3571
|
+
const sessions = s.labSessions.map((sess) => {
|
|
3572
|
+
if (sess.id !== sessionId) return sess;
|
|
3573
|
+
const msgs = [...sess.messages];
|
|
3574
|
+
msgs[msgs.length - 1] = { ...msgs[msgs.length - 1], reasoning: fullReasoning };
|
|
3575
|
+
return { ...sess, messages: msgs };
|
|
3576
|
+
});
|
|
3577
|
+
return { labSessions: sessions };
|
|
3578
|
+
});
|
|
3579
|
+
}
|
|
3517
3580
|
if (chunk.type === 'token' && chunk.content) {
|
|
3518
3581
|
if (!firstTokenTime) firstTokenTime = performance.now();
|
|
3519
3582
|
tokenCount++;
|
|
@@ -3640,6 +3703,8 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
3640
3703
|
}
|
|
3641
3704
|
try {
|
|
3642
3705
|
const data = await api.post('/lab/assistant', { backend });
|
|
3706
|
+
localStorage.setItem('groove:labAssistantAgentId', data.agentId);
|
|
3707
|
+
localStorage.setItem('groove:labAssistantBackend', backend);
|
|
3643
3708
|
set({ labAssistantAgentId: data.agentId, labAssistantMode: true, labAssistantBackend: backend });
|
|
3644
3709
|
get().addToast('info', `Lab Assistant started for ${backend}`);
|
|
3645
3710
|
} catch (err) {
|
|
@@ -3654,6 +3719,8 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
3654
3719
|
clearLabAssistant() {
|
|
3655
3720
|
const id = get().labAssistantAgentId;
|
|
3656
3721
|
if (id) api.delete(`/agents/${encodeURIComponent(id)}`).catch(() => {});
|
|
3722
|
+
localStorage.removeItem('groove:labAssistantAgentId');
|
|
3723
|
+
localStorage.removeItem('groove:labAssistantBackend');
|
|
3657
3724
|
set({ labAssistantAgentId: null, labAssistantMode: false, labAssistantBackend: null });
|
|
3658
3725
|
},
|
|
3659
3726
|
|
|
@@ -10,12 +10,11 @@ import { RootNode } from '../components/agents/root-node';
|
|
|
10
10
|
import { cn } from '../lib/cn';
|
|
11
11
|
import { Button } from '../components/ui/button';
|
|
12
12
|
import { Badge } from '../components/ui/badge';
|
|
13
|
-
import { Plus, Users, UserPlus, Zap, X, Check, Rocket, Server, Monitor, Code2, TestTube, Shield, Pencil, Copy, Trash2, ChevronDown, ChevronLeft, ChevronRight, FolderOpen, Eye, Settings2, Search, GripVertical, Cloud, FileText, Database, Megaphone, Calculator, UserCheck, Headphones, BarChart3, Pen, Presentation, Globe, MessageCircle, Save, Layers,
|
|
13
|
+
import { Plus, Users, UserPlus, Zap, X, Check, Rocket, Server, Monitor, Code2, TestTube, Shield, Pencil, Copy, Trash2, ChevronDown, ChevronLeft, ChevronRight, FolderOpen, Eye, Settings2, Search, GripVertical, Cloud, FileText, Database, Megaphone, Calculator, UserCheck, Headphones, BarChart3, Pen, Presentation, Globe, MessageCircle, Save, Layers, Box, HardDrive, LayoutGrid } from 'lucide-react';
|
|
14
14
|
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';
|
|
19
18
|
import { Select, SelectTrigger, SelectContent, SelectItem } from '../components/ui/select';
|
|
20
19
|
import { ScrollArea } from '../components/ui/scroll-area';
|
|
21
20
|
import { Tooltip } from '../components/ui/tooltip';
|
|
@@ -97,11 +96,9 @@ export function TeamTabBar() {
|
|
|
97
96
|
const agents = useGrooveStore((s) => s.agents);
|
|
98
97
|
const switchTeam = useGrooveStore((s) => s.switchTeam);
|
|
99
98
|
const createTeam = useGrooveStore((s) => s.createTeam);
|
|
100
|
-
const archiveTeam = useGrooveStore((s) => s.archiveTeam);
|
|
101
99
|
const deleteTeamPermanently = useGrooveStore((s) => s.deleteTeamPermanently);
|
|
102
100
|
const renameTeam = useGrooveStore((s) => s.renameTeam);
|
|
103
101
|
const cloneTeam = useGrooveStore((s) => s.cloneTeam);
|
|
104
|
-
const promoteTeam = useGrooveStore((s) => s.promoteTeam);
|
|
105
102
|
const reorderTeams = useGrooveStore((s) => s.reorderTeams);
|
|
106
103
|
const addToast = useGrooveStore((s) => s.addToast);
|
|
107
104
|
|
|
@@ -109,7 +106,6 @@ export function TeamTabBar() {
|
|
|
109
106
|
const [newName, setNewName] = useState('');
|
|
110
107
|
const [renamingId, setRenamingId] = useState(null);
|
|
111
108
|
const [renameValue, setRenameValue] = useState('');
|
|
112
|
-
const [archiveConfirm, setArchiveConfirm] = useState(null);
|
|
113
109
|
const submitting = useRef(false);
|
|
114
110
|
const [dragId, setDragId] = useState(null);
|
|
115
111
|
const [dragOverId, setDragOverId] = useState(null);
|
|
@@ -263,11 +259,6 @@ export function TeamTabBar() {
|
|
|
263
259
|
<ContextMenuItem onSelect={() => cloneTeam(team.id)}>
|
|
264
260
|
<Copy size={12} /> Clone
|
|
265
261
|
</ContextMenuItem>
|
|
266
|
-
{team.mode !== 'production' && (
|
|
267
|
-
<ContextMenuItem onSelect={() => promoteTeam(team.id)}>
|
|
268
|
-
<ArrowUpCircle size={12} /> Promote to Production
|
|
269
|
-
</ContextMenuItem>
|
|
270
|
-
)}
|
|
271
262
|
<ContextMenuSeparator />
|
|
272
263
|
<ContextMenuItem danger onSelect={() => {
|
|
273
264
|
const teamAgents = agents.filter((a) => a.teamId === team.id);
|
|
@@ -275,9 +266,9 @@ export function TeamTabBar() {
|
|
|
275
266
|
addToast('error', 'Stop running agents first');
|
|
276
267
|
return;
|
|
277
268
|
}
|
|
278
|
-
|
|
269
|
+
deleteTeamPermanently(team.id);
|
|
279
270
|
}}>
|
|
280
|
-
<Trash2 size={12} />
|
|
271
|
+
<Trash2 size={12} /> Delete
|
|
281
272
|
</ContextMenuItem>
|
|
282
273
|
</ContextMenuContent>
|
|
283
274
|
</ContextMenu>
|
|
@@ -325,14 +316,6 @@ export function TeamTabBar() {
|
|
|
325
316
|
</button>
|
|
326
317
|
)}
|
|
327
318
|
|
|
328
|
-
<TeamRemovalDialog
|
|
329
|
-
team={archiveConfirm}
|
|
330
|
-
open={!!archiveConfirm}
|
|
331
|
-
onOpenChange={(open) => !open && setArchiveConfirm(null)}
|
|
332
|
-
onArchive={archiveTeam}
|
|
333
|
-
onDeletePermanently={deleteTeamPermanently}
|
|
334
|
-
mode={archiveConfirm?.mode || 'sandbox'}
|
|
335
|
-
/>
|
|
336
319
|
</div>
|
|
337
320
|
);
|
|
338
321
|
}
|
|
@@ -1500,6 +1483,9 @@ export default function AgentsView() {
|
|
|
1500
1483
|
const showPreviewInAgents = useGrooveStore((s) => s.showPreviewInAgents);
|
|
1501
1484
|
const previewState = useGrooveStore((s) => s.previewState);
|
|
1502
1485
|
const togglePreviewInAgents = useGrooveStore((s) => s.togglePreviewInAgents);
|
|
1486
|
+
const teamPreviews = useGrooveStore((s) => s.teamPreviews);
|
|
1487
|
+
const relaunchPreview = useGrooveStore((s) => s.relaunchPreview);
|
|
1488
|
+
const openPreview = useGrooveStore((s) => s.openPreview);
|
|
1503
1489
|
const workspaceMode = useGrooveStore((s) => s.workspaceMode);
|
|
1504
1490
|
const setWorkspaceMode = useGrooveStore((s) => s.setWorkspaceMode);
|
|
1505
1491
|
const openTeamBuilder = useGrooveStore((s) => s.openTeamBuilder);
|
|
@@ -1569,7 +1555,7 @@ export default function AgentsView() {
|
|
|
1569
1555
|
<EmptyState onPlanner={openPlannerConfig} onSpawn={() => openDetail({ type: 'spawn' })} onTeamBuilder={openTeamBuilder} />
|
|
1570
1556
|
) : workspaceMode ? (
|
|
1571
1557
|
<WorkspaceMode />
|
|
1572
|
-
) : showPreviewInAgents && previewState.url && previewState.teamId === activeTeamId ? (
|
|
1558
|
+
) : showPreviewInAgents && previewState.url && previewState.teamId === activeTeamId && teamPreviews[activeTeamId]?.active ? (
|
|
1573
1559
|
<PreviewWorkspace embedded />
|
|
1574
1560
|
) : (
|
|
1575
1561
|
<ReactFlowProvider key={activeTeamId}>
|
|
@@ -1579,59 +1565,81 @@ export default function AgentsView() {
|
|
|
1579
1565
|
</div>
|
|
1580
1566
|
{!workspaceMode && <RecommendedTeamCard />}
|
|
1581
1567
|
{!isLoading && teamAgents.length > 0 && !workspaceMode && (
|
|
1582
|
-
<
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1568
|
+
<div className="absolute bottom-3 left-3 z-40 flex items-center gap-1">
|
|
1569
|
+
<Tooltip content="Spawn agent" side="top">
|
|
1570
|
+
<button
|
|
1571
|
+
onClick={() => openDetail({ type: 'spawn' })}
|
|
1572
|
+
className="flex items-center justify-center w-7 h-7 rounded-md text-text-3 hover:text-accent hover:bg-accent/10 transition-colors cursor-pointer"
|
|
1573
|
+
>
|
|
1574
|
+
<Plus size={15} />
|
|
1575
|
+
</button>
|
|
1576
|
+
</Tooltip>
|
|
1577
|
+
<Tooltip content={Object.keys(loadRoleLayout()).length > 0 ? 'Update layout' : 'Save layout'} side="top">
|
|
1578
|
+
<button
|
|
1579
|
+
onClick={() => {
|
|
1580
|
+
const positions = loadPositions(activeTeamId);
|
|
1581
|
+
const layout = {};
|
|
1582
|
+
const roleCounts = new Map();
|
|
1583
|
+
[...teamAgents].sort((a, b) => (a.name || a.id).localeCompare(b.name || b.id)).forEach((agent) => {
|
|
1584
|
+
const key = agent.name || agent.id;
|
|
1585
|
+
const pos = positions[key];
|
|
1586
|
+
if (!pos) return;
|
|
1587
|
+
const role = agent.role || 'agent';
|
|
1588
|
+
const count = roleCounts.get(role) || 0;
|
|
1589
|
+
roleCounts.set(role, count + 1);
|
|
1590
|
+
const roleKey = count === 0 ? role : `${role}-${count}`;
|
|
1591
|
+
layout[roleKey] = pos;
|
|
1592
|
+
});
|
|
1593
|
+
if (positions[ROOT_ID]) layout[ROOT_ID] = positions[ROOT_ID];
|
|
1594
|
+
saveRoleLayout(layout);
|
|
1595
|
+
addToast('success', 'Layout saved', 'Future spawns will use these positions');
|
|
1596
|
+
}}
|
|
1597
|
+
className="flex items-center justify-center w-7 h-7 rounded-md text-text-3 hover:text-accent hover:bg-accent/10 transition-colors cursor-pointer"
|
|
1598
|
+
>
|
|
1599
|
+
<LayoutGrid size={15} />
|
|
1600
|
+
</button>
|
|
1601
|
+
</Tooltip>
|
|
1602
|
+
<Tooltip content="Workspace" side="top">
|
|
1603
|
+
<button
|
|
1604
|
+
onClick={() => setWorkspaceMode(true)}
|
|
1605
|
+
className="flex items-center justify-center w-7 h-7 rounded-md text-text-3 hover:text-accent hover:bg-accent/10 transition-colors cursor-pointer"
|
|
1606
|
+
>
|
|
1607
|
+
<Code2 size={15} />
|
|
1608
|
+
</button>
|
|
1609
|
+
</Tooltip>
|
|
1610
|
+
{(() => {
|
|
1611
|
+
const tp = teamPreviews[activeTeamId];
|
|
1612
|
+
if (!tp) return null;
|
|
1613
|
+
const isActive = tp.active && previewState.url && previewState.teamId === activeTeamId;
|
|
1614
|
+
const isShowing = isActive && showPreviewInAgents;
|
|
1615
|
+
const tooltip = isShowing ? 'Show team' : isActive ? 'Show preview' : 'Relaunch preview';
|
|
1616
|
+
return (
|
|
1617
|
+
<Tooltip content={tooltip} side="top">
|
|
1618
|
+
<button
|
|
1619
|
+
onClick={() => {
|
|
1620
|
+
if (isActive) {
|
|
1621
|
+
if (showPreviewInAgents) {
|
|
1622
|
+
togglePreviewInAgents();
|
|
1623
|
+
} else {
|
|
1624
|
+
openPreview(tp.url, activeTeamId, tp.kind);
|
|
1625
|
+
}
|
|
1626
|
+
} else {
|
|
1627
|
+
relaunchPreview(activeTeamId);
|
|
1628
|
+
}
|
|
1629
|
+
}}
|
|
1630
|
+
className={cn(
|
|
1631
|
+
'flex items-center justify-center w-7 h-7 rounded-md transition-colors cursor-pointer',
|
|
1632
|
+
isActive
|
|
1633
|
+
? 'text-text-3 hover:text-info hover:bg-info/10'
|
|
1634
|
+
: 'text-text-4 hover:text-warning hover:bg-warning/10',
|
|
1635
|
+
)}
|
|
1636
|
+
>
|
|
1637
|
+
{isShowing ? <Users size={15} /> : <Eye size={15} />}
|
|
1638
|
+
</button>
|
|
1639
|
+
</Tooltip>
|
|
1640
|
+
);
|
|
1641
|
+
})()}
|
|
1642
|
+
</div>
|
|
1635
1643
|
)}
|
|
1636
1644
|
<PlannerConfigDialog open={plannerConfigOpen} onOpenChange={setPlannerConfigOpen} onLaunch={handlePlannerLaunch} />
|
|
1637
1645
|
<TeamBuilder />
|
|
@@ -117,17 +117,19 @@ export default function EditorView() {
|
|
|
117
117
|
/>
|
|
118
118
|
</div>
|
|
119
119
|
|
|
120
|
+
{/* Sidebar expand rail */}
|
|
121
|
+
{sidebarCollapsed && (
|
|
122
|
+
<button
|
|
123
|
+
onClick={() => setSidebarCollapsed(false)}
|
|
124
|
+
className="flex-shrink-0 w-6 flex items-center justify-center border-r border-border bg-surface-2 text-text-4 hover:text-text-0 hover:bg-surface-3 transition-colors cursor-pointer"
|
|
125
|
+
title="Show sidebar"
|
|
126
|
+
>
|
|
127
|
+
<PanelLeftOpen size={14} />
|
|
128
|
+
</button>
|
|
129
|
+
)}
|
|
130
|
+
|
|
120
131
|
{/* Editor area */}
|
|
121
132
|
<div className="flex-1 flex flex-col min-w-0 bg-surface-1">
|
|
122
|
-
{sidebarCollapsed && (
|
|
123
|
-
<button
|
|
124
|
-
onClick={() => setSidebarCollapsed(false)}
|
|
125
|
-
className="absolute top-2 left-2 z-10 w-7 h-7 flex items-center justify-center rounded-md text-text-3 hover:text-text-0 hover:bg-surface-3 transition-colors cursor-pointer"
|
|
126
|
-
title="Show sidebar"
|
|
127
|
-
>
|
|
128
|
-
<PanelLeftOpen size={15} />
|
|
129
|
-
</button>
|
|
130
|
-
)}
|
|
131
133
|
|
|
132
134
|
{/* Tab bar */}
|
|
133
135
|
<EditorTabs />
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { SUPPORTED_PROVIDERS, MODEL_TIERS, TRAINING_EXCLUSION_REASONS } from './constants.js';
|
|
4
4
|
|
|
5
|
-
export const STEP_TYPES = ['thought', 'action', 'observation', 'correction', 'resolution', 'error', 'coordination', 'edit', 'instruction', 'clarification', 'approval'];
|
|
5
|
+
export const STEP_TYPES = ['thought', 'action', 'observation', 'correction', 'resolution', 'error', 'coordination', 'edit', 'instruction', 'clarification', 'approval', 'delegate', 'yield'];
|
|
6
6
|
const VALID_QUALITY_TIERS = ['TIER_A', 'TIER_B', 'TIER_C'];
|
|
7
7
|
const VALID_FEEDBACK_SIGNALS = ['accepted', 'modified', 'rejected', 'iterated'];
|
|
8
8
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.134",
|
|
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)",
|
|
@@ -373,7 +373,9 @@ export class Daemon {
|
|
|
373
373
|
if (msg.rows !== undefined && (typeof msg.rows !== 'number' || msg.rows < 1 || msg.rows > 200)) break;
|
|
374
374
|
try {
|
|
375
375
|
const id = this.terminalManager.spawn(ws, { cwd: msg.cwd, cols: msg.cols, rows: msg.rows });
|
|
376
|
-
|
|
376
|
+
const spawned = { type: 'terminal:spawned', id };
|
|
377
|
+
if (msg.requestId) spawned.requestId = msg.requestId;
|
|
378
|
+
ws.send(JSON.stringify(spawned));
|
|
377
379
|
} catch (err) {
|
|
378
380
|
console.error('[terminal] spawn error:', err);
|
|
379
381
|
ws.send(JSON.stringify({ type: 'terminal:error', message: err.message }));
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// GROOVE — Introduction Protocol
|
|
2
2
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
3
3
|
|
|
4
|
-
import { writeFileSync, readFileSync, existsSync } from 'fs';
|
|
5
|
-
import { resolve } from 'path';
|
|
4
|
+
import { writeFileSync, readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
5
|
+
import { resolve, dirname, basename } from 'path';
|
|
6
6
|
import { escapeMd } from './validate.js';
|
|
7
7
|
|
|
8
8
|
const GROOVE_SECTION_START = '<!-- GROOVE:START -->';
|
|
@@ -28,7 +28,50 @@ export class Introducer {
|
|
|
28
28
|
];
|
|
29
29
|
|
|
30
30
|
if (newAgent.workingDir) {
|
|
31
|
-
lines.push(`Your working directory: \`${newAgent.workingDir}\` —
|
|
31
|
+
lines.push(`Your working directory: \`${newAgent.workingDir}\` — this is the team orchestration directory (.groove/, coordination files). Do NOT create source code or project files here.`);
|
|
32
|
+
|
|
33
|
+
// Inject parent directory context so agents know the root layout
|
|
34
|
+
const parentDir = dirname(newAgent.workingDir);
|
|
35
|
+
const teamDirName = basename(newAgent.workingDir);
|
|
36
|
+
lines.push(`Your project root: \`${parentDir}\` — all source code, features, and builds go here (one level up from team dir).`);
|
|
37
|
+
lines.push('');
|
|
38
|
+
lines.push('## Project Root Structure');
|
|
39
|
+
lines.push('');
|
|
40
|
+
lines.push(`Team dir: \`${teamDirName}/\` (orchestration only — do NOT build here)`);
|
|
41
|
+
lines.push(`Project root: \`${parentDir}\``);
|
|
42
|
+
lines.push('');
|
|
43
|
+
try {
|
|
44
|
+
const entries = readdirSync(parentDir, { withFileTypes: true });
|
|
45
|
+
const dirs = [];
|
|
46
|
+
const files = [];
|
|
47
|
+
for (const entry of entries) {
|
|
48
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
|
49
|
+
if (entry.name === teamDirName) continue;
|
|
50
|
+
if (entry.isDirectory()) {
|
|
51
|
+
dirs.push(entry.name + '/');
|
|
52
|
+
} else {
|
|
53
|
+
files.push(entry.name);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (dirs.length > 0) {
|
|
57
|
+
lines.push('Directories:');
|
|
58
|
+
for (const d of dirs.slice(0, 30)) {
|
|
59
|
+
lines.push(` ${d}`);
|
|
60
|
+
}
|
|
61
|
+
if (dirs.length > 30) lines.push(` (+${dirs.length - 30} more)`);
|
|
62
|
+
}
|
|
63
|
+
if (files.length > 0) {
|
|
64
|
+
lines.push('Files:');
|
|
65
|
+
for (const f of files.slice(0, 20)) {
|
|
66
|
+
lines.push(` ${f}`);
|
|
67
|
+
}
|
|
68
|
+
if (files.length > 20) lines.push(` (+${files.length - 20} more)`);
|
|
69
|
+
}
|
|
70
|
+
lines.push('');
|
|
71
|
+
lines.push('When creating or modifying project files, use "../" paths relative to the team dir (e.g., "../demo/src/app.js"). The team directory is ephemeral and may be deleted — never put project work inside it.');
|
|
72
|
+
} catch {
|
|
73
|
+
// Parent dir not readable — skip
|
|
74
|
+
}
|
|
32
75
|
}
|
|
33
76
|
|
|
34
77
|
if (newAgent.scope && newAgent.scope.length > 0) {
|
|
@@ -185,7 +228,8 @@ export class Introducer {
|
|
|
185
228
|
lines.push('');
|
|
186
229
|
lines.push(`CRITICAL: NEVER delete files you did not create in this session. Do NOT remove files from other projects, previous work, or unrelated directories.`);
|
|
187
230
|
if (newAgent.workingDir) {
|
|
188
|
-
|
|
231
|
+
const parentDir = dirname(newAgent.workingDir);
|
|
232
|
+
lines.push(`Your team directory is \`${newAgent.workingDir}\` (orchestration only). Build all project files in the project root: \`${parentDir}\`.`);
|
|
189
233
|
}
|
|
190
234
|
lines.push(`If you see files that seem unrelated to your task, leave them alone — they belong to another project or agent.`);
|
|
191
235
|
|
|
@@ -42,7 +42,7 @@ export class LlamaServerManager {
|
|
|
42
42
|
const server = this.servers.get(modelPath);
|
|
43
43
|
server.users++;
|
|
44
44
|
server.lastUsed = Date.now();
|
|
45
|
-
return `http://
|
|
45
|
+
return `http://localhost:${server.port}`;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// Check capacity
|
|
@@ -120,7 +120,7 @@ export class LlamaServerManager {
|
|
|
120
120
|
data: { modelPath, port },
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
-
return `http://
|
|
123
|
+
return `http://localhost:${port}`;
|
|
124
124
|
} catch (err) {
|
|
125
125
|
// Server failed to start
|
|
126
126
|
await this.stopServer(modelPath);
|
|
@@ -187,7 +187,7 @@ export class LlamaServerManager {
|
|
|
187
187
|
const start = Date.now();
|
|
188
188
|
while (Date.now() - start < HEALTH_TIMEOUT) {
|
|
189
189
|
try {
|
|
190
|
-
const res = await fetch(`http://
|
|
190
|
+
const res = await fetch(`http://localhost:${port}/health`, {
|
|
191
191
|
signal: AbortSignal.timeout(2000),
|
|
192
192
|
});
|
|
193
193
|
if (res.ok) {
|
|
@@ -209,7 +209,7 @@ export class LlamaServerManager {
|
|
|
209
209
|
if (!server) return { running: false };
|
|
210
210
|
|
|
211
211
|
try {
|
|
212
|
-
const res = await fetch(`http://
|
|
212
|
+
const res = await fetch(`http://localhost:${server.port}/health`, {
|
|
213
213
|
signal: AbortSignal.timeout(3000),
|
|
214
214
|
});
|
|
215
215
|
const data = await res.json().catch(() => ({}));
|
|
@@ -274,6 +274,14 @@ export class ModelLab {
|
|
|
274
274
|
try {
|
|
275
275
|
const chunk = JSON.parse(payload);
|
|
276
276
|
const delta = chunk.choices?.[0]?.delta;
|
|
277
|
+
if (delta?.reasoning_content) {
|
|
278
|
+
if (ttft === null) {
|
|
279
|
+
ttft = Date.now() - requestStart;
|
|
280
|
+
generationStart = Date.now();
|
|
281
|
+
}
|
|
282
|
+
completionTokens++;
|
|
283
|
+
yield { type: 'reasoning', content: delta.reasoning_content };
|
|
284
|
+
}
|
|
277
285
|
if (delta?.content) {
|
|
278
286
|
if (ttft === null) {
|
|
279
287
|
ttft = Date.now() - requestStart;
|