groove-dev 0.27.174 → 0.27.179
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/AUTONOMOUS_AGENT_FRAMEWORK.md +654 -0
- package/innerchat/Screenshot_2026-06-09_at_12.09.42_PM.png +0 -0
- package/innerchat/Screenshot_2026-06-09_at_2.19.42_PM.png +0 -0
- 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/api.js +3 -1
- package/node_modules/@groove-dev/daemon/src/autostate.js +198 -0
- package/node_modules/@groove-dev/daemon/src/index.js +6 -0
- package/node_modules/@groove-dev/daemon/src/orchestrator.js +585 -0
- package/node_modules/@groove-dev/daemon/src/process.js +27 -2
- package/node_modules/@groove-dev/daemon/src/registry.js +2 -1
- package/node_modules/@groove-dev/daemon/src/routes/auto-agents.js +264 -0
- package/node_modules/@groove-dev/daemon/src/validate.js +6 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-CTer01Vg.js +1065 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-DTFtRtkx.css +1 -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/agent-config.jsx +53 -0
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +4 -4
- package/node_modules/@groove-dev/gui/src/components/agents/agent-panel.jsx +95 -2
- package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +50 -1
- package/node_modules/@groove-dev/gui/src/components/auto-agents/auto-agent-card.jsx +139 -0
- package/node_modules/@groove-dev/gui/src/components/auto-agents/auto-agent-detail.jsx +286 -0
- package/node_modules/@groove-dev/gui/src/components/auto-agents/setup-wizard.jsx +284 -0
- package/node_modules/@groove-dev/gui/src/components/fleet/fleet-pane.jsx +16 -3
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +2 -1
- package/node_modules/@groove-dev/gui/src/stores/groove.js +14 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +2 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/auto-agents-slice.js +149 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/ui-slice.js +14 -11
- package/node_modules/@groove-dev/gui/src/views/auto-agents.jsx +70 -0
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +13 -6
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/api.js +3 -1
- package/packages/daemon/src/autostate.js +198 -0
- package/packages/daemon/src/index.js +6 -0
- package/packages/daemon/src/orchestrator.js +585 -0
- package/packages/daemon/src/process.js +27 -2
- package/packages/daemon/src/registry.js +2 -1
- package/packages/daemon/src/routes/auto-agents.js +264 -0
- package/packages/daemon/src/validate.js +6 -0
- package/packages/gui/dist/assets/index-CTer01Vg.js +1065 -0
- package/packages/gui/dist/assets/index-DTFtRtkx.css +1 -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/agent-config.jsx +53 -0
- package/packages/gui/src/components/agents/agent-feed.jsx +4 -4
- package/packages/gui/src/components/agents/agent-panel.jsx +95 -2
- package/packages/gui/src/components/agents/spawn-wizard.jsx +50 -1
- package/packages/gui/src/components/auto-agents/auto-agent-card.jsx +139 -0
- package/packages/gui/src/components/auto-agents/auto-agent-detail.jsx +286 -0
- package/packages/gui/src/components/auto-agents/setup-wizard.jsx +284 -0
- package/packages/gui/src/components/fleet/fleet-pane.jsx +16 -3
- package/packages/gui/src/components/layout/activity-bar.jsx +2 -1
- package/packages/gui/src/stores/groove.js +14 -0
- package/packages/gui/src/stores/slices/agents-slice.js +2 -0
- package/packages/gui/src/stores/slices/auto-agents-slice.js +149 -0
- package/packages/gui/src/stores/slices/ui-slice.js +14 -11
- package/packages/gui/src/views/auto-agents.jsx +70 -0
- package/packages/gui/src/views/settings.jsx +13 -6
- package/node_modules/@groove-dev/gui/dist/assets/index-BvJwMNAX.css +0 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-DZY-EWPs.js +0 -1030
- package/packages/gui/dist/assets/index-BvJwMNAX.css +0 -1
- package/packages/gui/dist/assets/index-DZY-EWPs.js +0 -1030
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
3
|
+
import { useGrooveStore } from '../../stores/groove';
|
|
4
|
+
import { Button } from '../ui/button';
|
|
5
|
+
import { Badge } from '../ui/badge';
|
|
6
|
+
import { Sheet, SheetContent } from '../ui/sheet';
|
|
7
|
+
import { cn } from '../../lib/cn';
|
|
8
|
+
import { api } from '../../lib/api';
|
|
9
|
+
import {
|
|
10
|
+
Bot, Send, ArrowRight, Sparkles, Clock, Shield, FileText,
|
|
11
|
+
ChevronDown, ChevronUp, Edit3, Check, X,
|
|
12
|
+
} from 'lucide-react';
|
|
13
|
+
|
|
14
|
+
const CADENCE_PRESETS = [
|
|
15
|
+
{ label: 'Every 15 min', value: '*/15 * * * *' },
|
|
16
|
+
{ label: 'Every 30 min', value: '*/30 * * * *' },
|
|
17
|
+
{ label: 'Every hour', value: '0 * * * *' },
|
|
18
|
+
{ label: 'Every 2 hours', value: '0 */2 * * *' },
|
|
19
|
+
{ label: 'Every 6 hours', value: '0 */6 * * *' },
|
|
20
|
+
{ label: 'Daily 9 AM', value: '0 9 * * *' },
|
|
21
|
+
{ label: 'Weekdays 9 AM', value: '0 9 * * 1-5' },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const SETUP_SYSTEM_PROMPT = `You are a setup assistant for creating autonomous AI agents in Groove.
|
|
25
|
+
Your job is to interview the user about what they want their auto agent to do, then generate a complete configuration.
|
|
26
|
+
|
|
27
|
+
Ask 3-5 focused questions to understand:
|
|
28
|
+
1. What is the agent's goal/objective?
|
|
29
|
+
2. What tools, APIs, or commands will it need?
|
|
30
|
+
3. What should it never do? (guardrails)
|
|
31
|
+
4. How often should it check in? (cadence)
|
|
32
|
+
5. What does success look like? (milestones/roadmap)
|
|
33
|
+
|
|
34
|
+
After gathering enough info, generate a JSON config block wrapped in \`\`\`json ... \`\`\` with this exact shape:
|
|
35
|
+
{
|
|
36
|
+
"ready": true,
|
|
37
|
+
"name": "short-kebab-name",
|
|
38
|
+
"description": "One-line description",
|
|
39
|
+
"cadence": "0 * * * *",
|
|
40
|
+
"role": "fullstack",
|
|
41
|
+
"prompt": "Full system prompt for the agent...",
|
|
42
|
+
"roadmap": "## Stage 1: ...\\n## Stage 2: ...",
|
|
43
|
+
"guardrails": ["Never do X", "Always do Y"],
|
|
44
|
+
"maxIterations": null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
Make the prompt comprehensive — include identity, philosophy, available tools, failure handling, and the never-park directive.
|
|
48
|
+
Make the roadmap have 2-4 stages with clear graduation criteria.
|
|
49
|
+
Keep the conversation friendly and efficient. Ask one question at a time.`;
|
|
50
|
+
|
|
51
|
+
function ChatMessage({ msg }) {
|
|
52
|
+
const isUser = msg.role === 'user';
|
|
53
|
+
return (
|
|
54
|
+
<div className={cn('flex gap-2', isUser ? 'justify-end' : 'justify-start')}>
|
|
55
|
+
{!isUser && (
|
|
56
|
+
<div className="w-6 h-6 rounded-full bg-accent/10 flex items-center justify-center flex-shrink-0 mt-0.5">
|
|
57
|
+
<Sparkles size={12} className="text-accent" />
|
|
58
|
+
</div>
|
|
59
|
+
)}
|
|
60
|
+
<div className={cn(
|
|
61
|
+
'max-w-[85%] rounded-lg px-3 py-2 text-xs font-sans',
|
|
62
|
+
isUser
|
|
63
|
+
? 'bg-accent text-surface-0 rounded-br-sm'
|
|
64
|
+
: 'bg-surface-2 text-text-1 rounded-bl-sm',
|
|
65
|
+
)}>
|
|
66
|
+
<p className="whitespace-pre-wrap">{msg.content}</p>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function ConfigPreview({ config, onDeploy, onEdit, deploying }) {
|
|
73
|
+
const [expanded, setExpanded] = useState(false);
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div className="rounded-lg border border-accent/30 bg-accent/5 p-4 space-y-3">
|
|
77
|
+
<div className="flex items-center gap-2">
|
|
78
|
+
<Bot size={16} className="text-accent" />
|
|
79
|
+
<span className="text-sm font-semibold text-text-0 font-sans">Ready to Deploy</span>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<div className="grid grid-cols-2 gap-2 text-xs font-sans">
|
|
83
|
+
<div>
|
|
84
|
+
<span className="text-text-4 text-2xs">Name</span>
|
|
85
|
+
<p className="text-text-0">{config.name}</p>
|
|
86
|
+
</div>
|
|
87
|
+
<div>
|
|
88
|
+
<span className="text-text-4 text-2xs">Cadence</span>
|
|
89
|
+
<p className="text-text-0">{CADENCE_PRESETS.find(p => p.value === config.cadence)?.label || config.cadence}</p>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
{config.description && (
|
|
94
|
+
<p className="text-xs text-text-2 font-sans">{config.description}</p>
|
|
95
|
+
)}
|
|
96
|
+
|
|
97
|
+
{config.guardrails?.length > 0 && (
|
|
98
|
+
<div>
|
|
99
|
+
<span className="text-2xs text-text-4 font-sans flex items-center gap-1">
|
|
100
|
+
<Shield size={10} /> Guardrails
|
|
101
|
+
</span>
|
|
102
|
+
<ul className="text-2xs text-text-2 font-sans mt-1 space-y-0.5">
|
|
103
|
+
{config.guardrails.map((g, i) => <li key={i}>• {g}</li>)}
|
|
104
|
+
</ul>
|
|
105
|
+
</div>
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
<button
|
|
109
|
+
onClick={() => setExpanded(!expanded)}
|
|
110
|
+
className="text-2xs text-accent font-sans flex items-center gap-1 cursor-pointer hover:underline"
|
|
111
|
+
>
|
|
112
|
+
{expanded ? <ChevronUp size={10} /> : <ChevronDown size={10} />}
|
|
113
|
+
{expanded ? 'Hide' : 'Show'} full config
|
|
114
|
+
</button>
|
|
115
|
+
|
|
116
|
+
{expanded && (
|
|
117
|
+
<pre className="text-2xs font-mono text-text-3 bg-surface-0 rounded p-2 overflow-x-auto max-h-48 overflow-y-auto border border-border-subtle">
|
|
118
|
+
{JSON.stringify(config, null, 2)}
|
|
119
|
+
</pre>
|
|
120
|
+
)}
|
|
121
|
+
|
|
122
|
+
<div className="flex gap-2 pt-1">
|
|
123
|
+
<Button variant="primary" size="sm" onClick={onDeploy} disabled={deploying} className="flex-1 gap-1.5">
|
|
124
|
+
{deploying ? 'Deploying...' : <><Check size={12} /> Deploy Agent</>}
|
|
125
|
+
</Button>
|
|
126
|
+
<Button variant="outline" size="sm" onClick={onEdit} className="gap-1.5">
|
|
127
|
+
<Edit3 size={12} /> Edit
|
|
128
|
+
</Button>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function AutoAgentSetupWizard() {
|
|
135
|
+
const open = useGrooveStore((s) => s.autoAgentWizardOpen);
|
|
136
|
+
const closeWizard = useGrooveStore((s) => s.closeAutoAgentWizard);
|
|
137
|
+
const createAutoAgent = useGrooveStore((s) => s.createAutoAgent);
|
|
138
|
+
const setActiveView = useGrooveStore((s) => s.setActiveView);
|
|
139
|
+
|
|
140
|
+
const [messages, setMessages] = useState([]);
|
|
141
|
+
const [input, setInput] = useState('');
|
|
142
|
+
const [loading, setLoading] = useState(false);
|
|
143
|
+
const [config, setConfig] = useState(null);
|
|
144
|
+
const [deploying, setDeploying] = useState(false);
|
|
145
|
+
const scrollRef = useRef(null);
|
|
146
|
+
const inputRef = useRef(null);
|
|
147
|
+
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (open && messages.length === 0) {
|
|
150
|
+
setMessages([{
|
|
151
|
+
role: 'assistant',
|
|
152
|
+
content: "Hey! I'll help you set up an autonomous agent. What task do you want it to handle? Tell me what you're trying to accomplish and I'll help configure everything.",
|
|
153
|
+
}]);
|
|
154
|
+
setConfig(null);
|
|
155
|
+
}
|
|
156
|
+
}, [open]);
|
|
157
|
+
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
if (scrollRef.current) {
|
|
160
|
+
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
161
|
+
}
|
|
162
|
+
}, [messages]);
|
|
163
|
+
|
|
164
|
+
const sendMessage = useCallback(async () => {
|
|
165
|
+
if (!input.trim() || loading) return;
|
|
166
|
+
const userMsg = { role: 'user', content: input.trim() };
|
|
167
|
+
const newMessages = [...messages, userMsg];
|
|
168
|
+
setMessages(newMessages);
|
|
169
|
+
setInput('');
|
|
170
|
+
setLoading(true);
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const chatHistory = newMessages.map(m => ({ role: m.role, content: m.content }));
|
|
174
|
+
const result = await api.post('/auto-agents/setup-chat', {
|
|
175
|
+
messages: chatHistory,
|
|
176
|
+
systemPrompt: SETUP_SYSTEM_PROMPT,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const assistantMsg = { role: 'assistant', content: result.response };
|
|
180
|
+
setMessages(prev => [...prev, assistantMsg]);
|
|
181
|
+
|
|
182
|
+
if (result.config) {
|
|
183
|
+
setConfig(result.config);
|
|
184
|
+
}
|
|
185
|
+
} catch (err) {
|
|
186
|
+
setMessages(prev => [...prev, {
|
|
187
|
+
role: 'assistant',
|
|
188
|
+
content: `Sorry, I hit an error: ${err.message}. Let me try again — could you rephrase that?`,
|
|
189
|
+
}]);
|
|
190
|
+
} finally {
|
|
191
|
+
setLoading(false);
|
|
192
|
+
}
|
|
193
|
+
}, [input, messages, loading]);
|
|
194
|
+
|
|
195
|
+
const handleDeploy = useCallback(async () => {
|
|
196
|
+
if (!config) return;
|
|
197
|
+
setDeploying(true);
|
|
198
|
+
try {
|
|
199
|
+
const result = await createAutoAgent(config);
|
|
200
|
+
if (result) {
|
|
201
|
+
setActiveView('auto-agents');
|
|
202
|
+
}
|
|
203
|
+
} finally {
|
|
204
|
+
setDeploying(false);
|
|
205
|
+
}
|
|
206
|
+
}, [config]);
|
|
207
|
+
|
|
208
|
+
const handleClose = () => {
|
|
209
|
+
closeWizard();
|
|
210
|
+
setMessages([]);
|
|
211
|
+
setConfig(null);
|
|
212
|
+
setInput('');
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<Sheet open={open} onOpenChange={(o) => { if (!o) handleClose(); }}>
|
|
217
|
+
<SheetContent title="New Auto Agent" width={520} onClose={handleClose}>
|
|
218
|
+
<div className="flex flex-col h-full">
|
|
219
|
+
{/* Chat area */}
|
|
220
|
+
<div ref={scrollRef} className="flex-1 overflow-y-auto px-4 py-4 space-y-3">
|
|
221
|
+
{messages.map((msg, i) => <ChatMessage key={i} msg={msg} />)}
|
|
222
|
+
|
|
223
|
+
{loading && (
|
|
224
|
+
<div className="flex gap-2">
|
|
225
|
+
<div className="w-6 h-6 rounded-full bg-accent/10 flex items-center justify-center flex-shrink-0">
|
|
226
|
+
<Sparkles size={12} className="text-accent animate-pulse" />
|
|
227
|
+
</div>
|
|
228
|
+
<div className="bg-surface-2 rounded-lg px-3 py-2 rounded-bl-sm">
|
|
229
|
+
<span className="text-xs text-text-3 font-sans animate-pulse">Thinking...</span>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
)}
|
|
233
|
+
|
|
234
|
+
{config && (
|
|
235
|
+
<ConfigPreview
|
|
236
|
+
config={config}
|
|
237
|
+
onDeploy={handleDeploy}
|
|
238
|
+
onEdit={() => setConfig(null)}
|
|
239
|
+
deploying={deploying}
|
|
240
|
+
/>
|
|
241
|
+
)}
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
{/* Input area */}
|
|
245
|
+
<div className="border-t border-border-subtle px-4 py-3">
|
|
246
|
+
<div className="flex items-center gap-2">
|
|
247
|
+
<input
|
|
248
|
+
ref={inputRef}
|
|
249
|
+
type="text"
|
|
250
|
+
value={input}
|
|
251
|
+
onChange={(e) => setInput(e.target.value)}
|
|
252
|
+
onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }}
|
|
253
|
+
placeholder={config ? 'Ask to adjust the config...' : 'Describe what you want the agent to do...'}
|
|
254
|
+
className="flex-1 bg-surface-0 border border-border-subtle rounded-md px-3 py-2 text-xs text-text-0 font-sans placeholder:text-text-4 focus:outline-none focus:border-accent"
|
|
255
|
+
disabled={loading}
|
|
256
|
+
/>
|
|
257
|
+
<Button
|
|
258
|
+
variant="primary"
|
|
259
|
+
size="icon-sm"
|
|
260
|
+
onClick={sendMessage}
|
|
261
|
+
disabled={!input.trim() || loading}
|
|
262
|
+
>
|
|
263
|
+
<Send size={14} />
|
|
264
|
+
</Button>
|
|
265
|
+
</div>
|
|
266
|
+
{!config && (
|
|
267
|
+
<div className="flex flex-wrap gap-1.5 mt-2">
|
|
268
|
+
{['ML training pipeline', 'Content generation', 'Data monitoring', 'Code review'].map(s => (
|
|
269
|
+
<button
|
|
270
|
+
key={s}
|
|
271
|
+
onClick={() => { setInput(s); inputRef.current?.focus(); }}
|
|
272
|
+
className="px-2 py-0.5 text-2xs font-sans text-text-3 bg-surface-2 rounded-full hover:bg-surface-3 cursor-pointer transition-colors"
|
|
273
|
+
>
|
|
274
|
+
{s}
|
|
275
|
+
</button>
|
|
276
|
+
))}
|
|
277
|
+
</div>
|
|
278
|
+
)}
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
</SheetContent>
|
|
282
|
+
</Sheet>
|
|
283
|
+
);
|
|
284
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
2
|
import { useEffect, useRef, useState } from 'react';
|
|
3
|
-
import { X } from 'lucide-react';
|
|
3
|
+
import { X, PanelRight } from 'lucide-react';
|
|
4
4
|
import { useGrooveStore } from '../../stores/groove';
|
|
5
5
|
import { cn } from '../../lib/cn';
|
|
6
6
|
import { Badge } from '../ui/badge';
|
|
@@ -27,7 +27,7 @@ const STATUS_LABEL = {
|
|
|
27
27
|
rotating: 'Rotating',
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
export function FleetPane({ agentId, paneIndex }) {
|
|
30
|
+
export function FleetPane({ agentId, paneIndex, readOnly = false }) {
|
|
31
31
|
const effectiveId = agentId || null;
|
|
32
32
|
const lastIdRef = useRef(effectiveId);
|
|
33
33
|
if (effectiveId) lastIdRef.current = effectiveId;
|
|
@@ -36,6 +36,9 @@ export function FleetPane({ agentId, paneIndex }) {
|
|
|
36
36
|
const liveAgent = useGrooveStore((s) => resolvedId ? s.agents.find((a) => a.id === resolvedId) : null);
|
|
37
37
|
const fleetSelectAgent = useGrooveStore((s) => s.fleetSelectAgent);
|
|
38
38
|
const fleetMarkRead = useGrooveStore((s) => s.fleetMarkRead);
|
|
39
|
+
const openDetail = useGrooveStore((s) => s.openDetail);
|
|
40
|
+
const detailPanel = useGrooveStore((s) => s.detailPanel);
|
|
41
|
+
const isPanelOpen = detailPanel?.type === 'agent' && detailPanel?.agentId === resolvedId;
|
|
39
42
|
|
|
40
43
|
const lastAgentRef = useRef(liveAgent);
|
|
41
44
|
const [gone, setGone] = useState(false);
|
|
@@ -92,6 +95,16 @@ export function FleetPane({ agentId, paneIndex }) {
|
|
|
92
95
|
)}>
|
|
93
96
|
{ctxPct}%
|
|
94
97
|
</span>
|
|
98
|
+
<button
|
|
99
|
+
onClick={() => openDetail({ type: 'agent', agentId: resolvedId })}
|
|
100
|
+
className={cn(
|
|
101
|
+
'p-1 rounded-md transition-colors cursor-pointer',
|
|
102
|
+
isPanelOpen ? 'text-accent' : 'text-text-3 hover:text-text-0 hover:bg-surface-3',
|
|
103
|
+
)}
|
|
104
|
+
title="Open agent panel"
|
|
105
|
+
>
|
|
106
|
+
<PanelRight size={14} />
|
|
107
|
+
</button>
|
|
95
108
|
<button
|
|
96
109
|
onClick={() => fleetSelectAgent(null, paneIndex)}
|
|
97
110
|
className="p-1 rounded-md text-text-3 hover:text-text-0 hover:bg-surface-3 transition-colors cursor-pointer"
|
|
@@ -103,7 +116,7 @@ export function FleetPane({ agentId, paneIndex }) {
|
|
|
103
116
|
|
|
104
117
|
{/* Agent feed */}
|
|
105
118
|
<div className="flex-1 min-h-0">
|
|
106
|
-
<AgentFeed agent={agent} />
|
|
119
|
+
<AgentFeed agent={agent} readOnly={readOnly} />
|
|
107
120
|
</div>
|
|
108
121
|
</div>
|
|
109
122
|
);
|
|
@@ -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, LayoutList } from 'lucide-react';
|
|
2
|
+
import { Network, Code2, ChartSpline, Puzzle, Users, Box, FlaskConical, Newspaper, Settings, Globe, MessageCircle, BookOpen, LayoutList, Bot } from 'lucide-react';
|
|
3
3
|
import { cn } from '../../lib/cn';
|
|
4
4
|
import { Tooltip } from '../ui/tooltip';
|
|
5
5
|
import { useGrooveStore } from '../../stores/groove';
|
|
@@ -12,6 +12,7 @@ const BASE_NAV_ITEMS = [
|
|
|
12
12
|
{ id: 'editor', icon: Code2, label: 'Editor' },
|
|
13
13
|
{ id: 'dashboard', icon: ChartSpline, label: 'Dashboard' },
|
|
14
14
|
{ id: 'memory', icon: BookOpen, label: 'Memory' },
|
|
15
|
+
{ id: 'auto-agents', icon: Bot, label: 'Auto Agents' },
|
|
15
16
|
{ id: 'teams', icon: Users, label: 'Teams' },
|
|
16
17
|
{ id: 'marketplace', icon: Puzzle, label: 'Marketplace' },
|
|
17
18
|
{ id: 'models', icon: Box, label: 'Models' },
|
|
@@ -14,6 +14,7 @@ import { createNetworkSlice } from './slices/network-slice.js';
|
|
|
14
14
|
import { createPreviewSlice } from './slices/preview-slice.js';
|
|
15
15
|
import { createMarketplaceSlice } from './slices/marketplace-slice.js';
|
|
16
16
|
import { createAutomationsSlice } from './slices/automations-slice.js';
|
|
17
|
+
import { createAutoAgentsSlice } from './slices/auto-agents-slice.js';
|
|
17
18
|
|
|
18
19
|
const WS_URL = `ws://${window.location.hostname}:${window.location.port || 31415}`;
|
|
19
20
|
|
|
@@ -39,6 +40,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
39
40
|
...createPreviewSlice(set, get),
|
|
40
41
|
...createMarketplaceSlice(set, get),
|
|
41
42
|
...createAutomationsSlice(set, get),
|
|
43
|
+
...createAutoAgentsSlice(set, get),
|
|
42
44
|
|
|
43
45
|
// ── Connection ────────────────────────────────────────────
|
|
44
46
|
connected: false,
|
|
@@ -654,6 +656,18 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
654
656
|
get().fetchAutomations();
|
|
655
657
|
break;
|
|
656
658
|
|
|
659
|
+
case 'auto-agent:created':
|
|
660
|
+
case 'auto-agent:updated':
|
|
661
|
+
case 'auto-agent:deleted':
|
|
662
|
+
case 'auto-agent:paused':
|
|
663
|
+
case 'auto-agent:resumed':
|
|
664
|
+
case 'auto-agent:cycle-complete':
|
|
665
|
+
case 'auto-agent:auto-paused':
|
|
666
|
+
case 'auto-agent:state-updated':
|
|
667
|
+
case 'auto-agent:iteration-started':
|
|
668
|
+
get().fetchAutoAgents();
|
|
669
|
+
break;
|
|
670
|
+
|
|
657
671
|
case 'gateway:status':
|
|
658
672
|
set({ gateways: msg.data || [] });
|
|
659
673
|
break;
|
|
@@ -38,6 +38,8 @@ export const createAgentsSlice = (set, get) => ({
|
|
|
38
38
|
// ── Agent Actions ─────────────────────────────────────────
|
|
39
39
|
|
|
40
40
|
selectAgent(id) {
|
|
41
|
+
// In fleet view, selection is managed by fleetSelectAgent — don't override detail panel here
|
|
42
|
+
if (get().activeView === 'fleet') return;
|
|
41
43
|
const tid = get().activeTeamId;
|
|
42
44
|
const match = get().agents.find((a) => a.id === id);
|
|
43
45
|
if (tid && match && match.teamId && match.teamId !== tid) return;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
|
|
3
|
+
import { api } from '../../lib/api';
|
|
4
|
+
|
|
5
|
+
export const createAutoAgentsSlice = (set, get) => ({
|
|
6
|
+
autoAgents: [],
|
|
7
|
+
autoAgentWizardOpen: false,
|
|
8
|
+
autoAgentWizardStep: 'chat',
|
|
9
|
+
autoAgentSetupMessages: [],
|
|
10
|
+
autoAgentSetupConfig: null,
|
|
11
|
+
selectedAutoAgentId: null,
|
|
12
|
+
|
|
13
|
+
async fetchAutoAgents() {
|
|
14
|
+
try {
|
|
15
|
+
const data = await api.get('/auto-agents');
|
|
16
|
+
set({ autoAgents: Array.isArray(data) ? data : [] });
|
|
17
|
+
} catch { /* ignore */ }
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
async createAutoAgent(config) {
|
|
21
|
+
try {
|
|
22
|
+
const result = await api.post('/auto-agents', config);
|
|
23
|
+
get().addToast('success', 'Auto agent created');
|
|
24
|
+
get().fetchAutoAgents();
|
|
25
|
+
set({ autoAgentWizardOpen: false, autoAgentSetupMessages: [], autoAgentSetupConfig: null });
|
|
26
|
+
return result;
|
|
27
|
+
} catch (err) {
|
|
28
|
+
get().addToast('error', 'Failed to create auto agent', err.message);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
async updateAutoAgent(id, updates) {
|
|
33
|
+
try {
|
|
34
|
+
await api.patch(`/auto-agents/${encodeURIComponent(id)}`, updates);
|
|
35
|
+
get().fetchAutoAgents();
|
|
36
|
+
} catch (err) {
|
|
37
|
+
get().addToast('error', 'Failed to update', err.message);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
async deleteAutoAgent(id) {
|
|
42
|
+
try {
|
|
43
|
+
await api.delete(`/auto-agents/${encodeURIComponent(id)}`);
|
|
44
|
+
get().addToast('info', 'Auto agent deleted');
|
|
45
|
+
get().fetchAutoAgents();
|
|
46
|
+
if (get().selectedAutoAgentId === id) set({ selectedAutoAgentId: null });
|
|
47
|
+
} catch (err) {
|
|
48
|
+
get().addToast('error', 'Failed to delete', err.message);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
async pauseAutoAgent(id) {
|
|
53
|
+
try {
|
|
54
|
+
await api.post(`/auto-agents/${encodeURIComponent(id)}/pause`);
|
|
55
|
+
get().fetchAutoAgents();
|
|
56
|
+
} catch (err) {
|
|
57
|
+
get().addToast('error', 'Failed to pause', err.message);
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
async resumeAutoAgent(id) {
|
|
62
|
+
try {
|
|
63
|
+
await api.post(`/auto-agents/${encodeURIComponent(id)}/resume`);
|
|
64
|
+
get().fetchAutoAgents();
|
|
65
|
+
} catch (err) {
|
|
66
|
+
get().addToast('error', 'Failed to resume', err.message);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
async triggerAutoAgent(id) {
|
|
71
|
+
try {
|
|
72
|
+
const result = await api.post(`/auto-agents/${encodeURIComponent(id)}/trigger`);
|
|
73
|
+
get().addToast('success', 'Iteration triggered');
|
|
74
|
+
get().fetchAutoAgents();
|
|
75
|
+
return result;
|
|
76
|
+
} catch (err) {
|
|
77
|
+
get().addToast('error', 'Failed to trigger', err.message);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
async fetchAutoAgentDetail(id) {
|
|
82
|
+
try {
|
|
83
|
+
return await api.get(`/auto-agents/${encodeURIComponent(id)}`);
|
|
84
|
+
} catch { return null; }
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
async fetchAutoAgentJournal(id) {
|
|
88
|
+
try {
|
|
89
|
+
return await api.get(`/auto-agents/${encodeURIComponent(id)}/journal?limit=100`);
|
|
90
|
+
} catch { return []; }
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
async fetchAutoAgentRuns(id) {
|
|
94
|
+
try {
|
|
95
|
+
return await api.get(`/auto-agents/${encodeURIComponent(id)}/runs?limit=30`);
|
|
96
|
+
} catch { return []; }
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
async fetchAutoAgentRoadmap(id) {
|
|
100
|
+
try {
|
|
101
|
+
const res = await fetch(`/api/auto-agents/${encodeURIComponent(id)}/roadmap`);
|
|
102
|
+
return await res.text();
|
|
103
|
+
} catch { return ''; }
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
async fetchAutoAgentPrompt(id) {
|
|
107
|
+
try {
|
|
108
|
+
const res = await fetch(`/api/auto-agents/${encodeURIComponent(id)}/prompt`);
|
|
109
|
+
return await res.text();
|
|
110
|
+
} catch { return ''; }
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
async updateAutoAgentPrompt(id, content) {
|
|
114
|
+
try {
|
|
115
|
+
await api.put(`/auto-agents/${encodeURIComponent(id)}/prompt`, { content });
|
|
116
|
+
get().addToast('success', 'Prompt updated');
|
|
117
|
+
} catch (err) {
|
|
118
|
+
get().addToast('error', 'Failed to update prompt', err.message);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
async updateAutoAgentRoadmap(id, content) {
|
|
123
|
+
try {
|
|
124
|
+
await api.put(`/auto-agents/${encodeURIComponent(id)}/roadmap`, { content });
|
|
125
|
+
get().addToast('success', 'Roadmap updated');
|
|
126
|
+
} catch (err) {
|
|
127
|
+
get().addToast('error', 'Failed to update roadmap', err.message);
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
openAutoAgentWizard() {
|
|
132
|
+
set({
|
|
133
|
+
autoAgentWizardOpen: true,
|
|
134
|
+
autoAgentWizardStep: 'chat',
|
|
135
|
+
autoAgentSetupMessages: [],
|
|
136
|
+
autoAgentSetupConfig: null,
|
|
137
|
+
});
|
|
138
|
+
},
|
|
139
|
+
closeAutoAgentWizard() {
|
|
140
|
+
set({ autoAgentWizardOpen: false, autoAgentSetupMessages: [], autoAgentSetupConfig: null });
|
|
141
|
+
},
|
|
142
|
+
selectAutoAgent(id) { set({ selectedAutoAgentId: id }); },
|
|
143
|
+
|
|
144
|
+
addAutoAgentSetupMessage(msg) {
|
|
145
|
+
set((s) => ({ autoAgentSetupMessages: [...s.autoAgentSetupMessages, msg] }));
|
|
146
|
+
},
|
|
147
|
+
setAutoAgentSetupConfig(config) { set({ autoAgentSetupConfig: config }); },
|
|
148
|
+
setAutoAgentWizardStep(step) { set({ autoAgentWizardStep: step }); },
|
|
149
|
+
});
|
|
@@ -6,7 +6,7 @@ let toastCounter = 0;
|
|
|
6
6
|
|
|
7
7
|
export const createUiSlice = (set, get) => ({
|
|
8
8
|
// ── Navigation ────────────────────────────────────────────
|
|
9
|
-
activeView: 'agents', // 'agents' | 'editor' | 'dashboard' | 'marketplace' | 'teams' | 'settings'
|
|
9
|
+
activeView: 'agents', // 'agents' | 'editor' | 'dashboard' | 'marketplace' | 'teams' | 'auto-agents' | 'settings'
|
|
10
10
|
detailPanel: null, // null | { type: 'agent', agentId } | { type: 'spawn' } | { type: 'journalist' }
|
|
11
11
|
teamDetailPanels: {}, // { [teamId]: detailPanel } — persists panel state per team
|
|
12
12
|
commandPaletteOpen: false,
|
|
@@ -91,7 +91,19 @@ export const createUiSlice = (set, get) => ({
|
|
|
91
91
|
localStorage.setItem('groove:detailWidth', String(w));
|
|
92
92
|
},
|
|
93
93
|
setTerminalVisible(v) {
|
|
94
|
-
|
|
94
|
+
const updates = { terminalVisible: v };
|
|
95
|
+
// In fleet view, opening the terminal auto-opens the agent panel so the
|
|
96
|
+
// terminal is constrained to the left column rather than spanning full width.
|
|
97
|
+
if (v && get().activeView === 'fleet') {
|
|
98
|
+
const selectedId = get().fleetSelectedAgents[0];
|
|
99
|
+
if (selectedId && !get().detailPanel) {
|
|
100
|
+
const tid = get().activeTeamId;
|
|
101
|
+
const panel = { type: 'agent', agentId: selectedId };
|
|
102
|
+
updates.detailPanel = panel;
|
|
103
|
+
updates.teamDetailPanels = { ...get().teamDetailPanels, [tid]: panel };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
set(updates);
|
|
95
107
|
localStorage.setItem('groove:terminalVisible', String(v));
|
|
96
108
|
},
|
|
97
109
|
setTerminalHeight(h) {
|
|
@@ -120,15 +132,6 @@ export const createUiSlice = (set, get) => ({
|
|
|
120
132
|
if (pane === 1 && agentId === null) {
|
|
121
133
|
updates.fleetSplitMode = false;
|
|
122
134
|
}
|
|
123
|
-
const primaryId = agentId || selected[0];
|
|
124
|
-
if (primaryId) {
|
|
125
|
-
const tid = get().activeTeamId;
|
|
126
|
-
const panel = { type: 'agent', agentId: primaryId };
|
|
127
|
-
updates.detailPanel = panel;
|
|
128
|
-
updates.teamDetailPanels = { ...get().teamDetailPanels, [tid]: panel };
|
|
129
|
-
} else {
|
|
130
|
-
updates.detailPanel = null;
|
|
131
|
-
}
|
|
132
135
|
set(updates);
|
|
133
136
|
},
|
|
134
137
|
fleetToggleSplit() {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
import { useGrooveStore } from '../stores/groove';
|
|
4
|
+
import { Button } from '../components/ui/button';
|
|
5
|
+
import { AutoAgentCard } from '../components/auto-agents/auto-agent-card';
|
|
6
|
+
import { AutoAgentDetail } from '../components/auto-agents/auto-agent-detail';
|
|
7
|
+
import { AutoAgentSetupWizard } from '../components/auto-agents/setup-wizard';
|
|
8
|
+
import { Plus, Bot } from 'lucide-react';
|
|
9
|
+
|
|
10
|
+
export default function AutoAgentsView() {
|
|
11
|
+
const autoAgents = useGrooveStore((s) => s.autoAgents);
|
|
12
|
+
const fetchAutoAgents = useGrooveStore((s) => s.fetchAutoAgents);
|
|
13
|
+
const openWizard = useGrooveStore((s) => s.openAutoAgentWizard);
|
|
14
|
+
const selectedId = useGrooveStore((s) => s.selectedAutoAgentId);
|
|
15
|
+
const selectAutoAgent = useGrooveStore((s) => s.selectAutoAgent);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
fetchAutoAgents();
|
|
19
|
+
const interval = setInterval(fetchAutoAgents, 10000);
|
|
20
|
+
return () => clearInterval(interval);
|
|
21
|
+
}, []);
|
|
22
|
+
|
|
23
|
+
// Detail view
|
|
24
|
+
if (selectedId) {
|
|
25
|
+
return (
|
|
26
|
+
<div className="flex flex-col h-full">
|
|
27
|
+
<AutoAgentDetail agentId={selectedId} onBack={() => selectAutoAgent(null)} />
|
|
28
|
+
<AutoAgentSetupWizard />
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="flex flex-col h-full">
|
|
35
|
+
{/* Header */}
|
|
36
|
+
<div className="flex items-center justify-between px-5 py-4 border-b border-border-subtle bg-surface-1">
|
|
37
|
+
<h2 className="text-sm font-semibold text-text-0 font-sans uppercase tracking-wide">Auto Agents</h2>
|
|
38
|
+
<Button variant="primary" size="sm" onClick={openWizard} className="gap-1.5">
|
|
39
|
+
<Plus size={12} /> New Auto Agent
|
|
40
|
+
</Button>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
{/* Content */}
|
|
44
|
+
<div className="flex-1 overflow-y-auto">
|
|
45
|
+
{autoAgents.length === 0 ? (
|
|
46
|
+
<div className="flex-1 flex items-center justify-center h-full">
|
|
47
|
+
<div className="text-center space-y-3 py-20">
|
|
48
|
+
<Bot size={28} className="mx-auto text-text-4" />
|
|
49
|
+
<p className="text-xs font-sans text-text-3">No auto agents yet</p>
|
|
50
|
+
<p className="text-2xs font-sans text-text-4 max-w-xs mx-auto">
|
|
51
|
+
Auto agents run autonomously on a schedule — iterating, learning, and making progress on complex tasks 24/7.
|
|
52
|
+
</p>
|
|
53
|
+
<Button variant="primary" size="sm" onClick={openWizard} className="gap-1.5 mt-2">
|
|
54
|
+
<Plus size={12} /> Create Auto Agent
|
|
55
|
+
</Button>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
) : (
|
|
59
|
+
<div className="p-4 space-y-3">
|
|
60
|
+
{autoAgents.map((a) => (
|
|
61
|
+
<AutoAgentCard key={a.id} agent={a} />
|
|
62
|
+
))}
|
|
63
|
+
</div>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<AutoAgentSetupWizard />
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|