groove-dev 0.25.15 → 0.25.17
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/daemon/src/api.js +31 -0
- package/node_modules/@groove-dev/gui/dist/assets/{index-Cg1mJi9s.js → index-Ca4wKXQ9.js} +19 -19
- package/node_modules/@groove-dev/gui/dist/index.html +1 -1
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +45 -9
- package/package.json +1 -1
- package/packages/daemon/src/api.js +31 -0
- package/packages/gui/dist/assets/{index-Cg1mJi9s.js → index-Ca4wKXQ9.js} +19 -19
- package/packages/gui/dist/index.html +1 -1
- package/packages/gui/src/components/agents/agent-feed.jsx +45 -9
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
7
7
|
<title>Groove GUI</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-Ca4wKXQ9.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/vendor-C0HXlhrU.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/reactflow-BQPfi37R.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/codemirror-BBL3i_JW.js">
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import { useGrooveStore } from '../../stores/groove';
|
|
10
10
|
import { cn } from '../../lib/cn';
|
|
11
11
|
import { timeAgo } from '../../lib/format';
|
|
12
|
+
import { api } from '../../lib/api';
|
|
12
13
|
|
|
13
14
|
const EMPTY = [];
|
|
14
15
|
|
|
@@ -294,18 +295,29 @@ function ActivityLine({ entry }) {
|
|
|
294
295
|
);
|
|
295
296
|
}
|
|
296
297
|
|
|
297
|
-
function ActivityGroup({ entries }) {
|
|
298
|
+
function ActivityGroup({ entries, isLive }) {
|
|
298
299
|
const [cycleIdx, setCycleIdx] = useState(0);
|
|
299
300
|
|
|
300
|
-
// Cycle through entries every 1.5s
|
|
301
301
|
useEffect(() => {
|
|
302
|
-
if (entries.length <= 1) return;
|
|
302
|
+
if (!isLive || entries.length <= 1) return;
|
|
303
303
|
const timer = setInterval(() => setCycleIdx((i) => (i + 1) % entries.length), 1500);
|
|
304
304
|
return () => clearInterval(timer);
|
|
305
|
-
}, [entries.length]);
|
|
305
|
+
}, [entries.length, isLive]);
|
|
306
|
+
|
|
307
|
+
if (!isLive) {
|
|
308
|
+
// Collapsed static summary for completed groups
|
|
309
|
+
const last = entries[entries.length - 1];
|
|
310
|
+
const meta = activityMeta(last.text);
|
|
311
|
+
const Icon = meta.icon;
|
|
312
|
+
return (
|
|
313
|
+
<div className="ml-7 flex items-center gap-2 px-3 py-1 text-[10px] text-text-4 font-mono">
|
|
314
|
+
<Icon size={10} className="opacity-50" />
|
|
315
|
+
<span className="truncate">{entries.length} tool call{entries.length !== 1 ? 's' : ''}</span>
|
|
316
|
+
</div>
|
|
317
|
+
);
|
|
318
|
+
}
|
|
306
319
|
|
|
307
320
|
const current = entries[Math.min(cycleIdx, entries.length - 1)];
|
|
308
|
-
const meta = activityMeta(current.text);
|
|
309
321
|
const display = current.text?.length > 60 ? current.text.slice(0, 60) + '...' : current.text;
|
|
310
322
|
|
|
311
323
|
return (
|
|
@@ -521,11 +533,33 @@ export function AgentFeed({ agent }) {
|
|
|
521
533
|
}
|
|
522
534
|
}, [timeline.length]);
|
|
523
535
|
|
|
524
|
-
function handleFileSelect(e) {
|
|
536
|
+
async function handleFileSelect(e) {
|
|
525
537
|
const files = Array.from(e.target.files || []);
|
|
526
538
|
if (files.length === 0) return;
|
|
527
|
-
const
|
|
528
|
-
|
|
539
|
+
const addToast = useGrooveStore.getState().addToast;
|
|
540
|
+
|
|
541
|
+
const uploaded = [];
|
|
542
|
+
for (const file of files) {
|
|
543
|
+
try {
|
|
544
|
+
const base64 = await new Promise((resolve, reject) => {
|
|
545
|
+
const reader = new FileReader();
|
|
546
|
+
reader.onload = () => resolve(reader.result.split(',')[1]); // strip data:...;base64,
|
|
547
|
+
reader.onerror = reject;
|
|
548
|
+
reader.readAsDataURL(file);
|
|
549
|
+
});
|
|
550
|
+
await api.post(`/agents/${agent.id}/upload`, { filename: file.name, content: base64 });
|
|
551
|
+
uploaded.push(file.name);
|
|
552
|
+
} catch (err) {
|
|
553
|
+
addToast('error', `Upload failed: ${file.name}`, err.message);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (uploaded.length > 0) {
|
|
558
|
+
const names = uploaded.join(', ');
|
|
559
|
+
setInput((prev) => (prev ? prev + '\n' : '') + `[Uploaded: ${names}] — I've uploaded these files to your working directory. Read them and use their content.`);
|
|
560
|
+
addToast('success', `Uploaded ${uploaded.length} file${uploaded.length > 1 ? 's' : ''}`);
|
|
561
|
+
}
|
|
562
|
+
|
|
529
563
|
e.target.value = '';
|
|
530
564
|
inputRef.current?.focus();
|
|
531
565
|
}
|
|
@@ -584,7 +618,9 @@ export function AgentFeed({ agent }) {
|
|
|
584
618
|
)}
|
|
585
619
|
{timeline.map((item, i) => {
|
|
586
620
|
if (item.kind === 'activity-group') {
|
|
587
|
-
|
|
621
|
+
// Only the last activity group is "live" if agent is still running
|
|
622
|
+
const isLastGroup = !timeline.slice(i + 1).some((t) => t.kind === 'activity-group' || t.from === 'agent');
|
|
623
|
+
return <ActivityGroup key={`grp-${i}`} entries={item.entries} isLive={isAlive && isLastGroup} />;
|
|
588
624
|
}
|
|
589
625
|
if (item.from === 'user') return <UserMessage key={`msg-${i}`} msg={item} />;
|
|
590
626
|
if (item.from === 'system') return <SystemMessage key={`msg-${i}`} msg={item} />;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.17",
|
|
4
4
|
"description": "Open-source agent orchestration layer — the AI company OS. 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.",
|
|
5
5
|
"license": "FSL-1.1-Apache-2.0",
|
|
6
6
|
"author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",
|
|
@@ -420,6 +420,37 @@ export function createApi(app, daemon) {
|
|
|
420
420
|
}
|
|
421
421
|
});
|
|
422
422
|
|
|
423
|
+
// Upload file to agent's working directory
|
|
424
|
+
app.post('/api/agents/:id/upload', (req, res) => {
|
|
425
|
+
const agent = daemon.registry.get(req.params.id);
|
|
426
|
+
if (!agent) return res.status(404).json({ error: 'Agent not found' });
|
|
427
|
+
|
|
428
|
+
const { filename, content } = req.body;
|
|
429
|
+
if (!filename || !content) return res.status(400).json({ error: 'filename and content required' });
|
|
430
|
+
|
|
431
|
+
// Sanitize filename — no path traversal
|
|
432
|
+
const safeName = String(filename).replace(/[/\\]/g, '_').replace(/\.\./g, '');
|
|
433
|
+
if (!safeName) return res.status(400).json({ error: 'Invalid filename' });
|
|
434
|
+
|
|
435
|
+
const dir = agent.workingDir || daemon.projectDir;
|
|
436
|
+
const filePath = resolve(dir, safeName);
|
|
437
|
+
|
|
438
|
+
// Ensure file stays within working directory
|
|
439
|
+
if (!filePath.startsWith(dir)) {
|
|
440
|
+
return res.status(400).json({ error: 'Path traversal detected' });
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
try {
|
|
444
|
+
mkdirSync(dir, { recursive: true });
|
|
445
|
+
const buffer = Buffer.from(content, 'base64');
|
|
446
|
+
writeFileSync(filePath, buffer);
|
|
447
|
+
daemon.audit.log('file.upload', { agentId: agent.id, filename: safeName, size: buffer.length });
|
|
448
|
+
res.json({ ok: true, path: safeName, size: buffer.length });
|
|
449
|
+
} catch (err) {
|
|
450
|
+
res.status(500).json({ error: `Upload failed: ${err.message}` });
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
|
|
423
454
|
// List MD files for an agent (from its working directory + .groove)
|
|
424
455
|
app.get('/api/agents/:id/mdfiles', (req, res) => {
|
|
425
456
|
const agent = daemon.registry.get(req.params.id);
|