groove-dev 0.27.153 → 0.27.154
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/journalist.js +52 -21
- package/node_modules/@groove-dev/daemon/src/keeper.js +37 -2
- package/node_modules/@groove-dev/daemon/src/routes/coordination.js +16 -0
- package/node_modules/@groove-dev/daemon/src/routes/files.js +71 -2
- package/node_modules/@groove-dev/gui/dist/assets/index-BTLb6zTD.js +1015 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-Diw6wDPU.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/components/agents/agent-feed.jsx +252 -44
- package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +51 -3
- package/node_modules/@groove-dev/gui/src/components/editor/file-tree.jsx +40 -3
- package/node_modules/@groove-dev/gui/src/stores/groove.js +9 -1
- package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +24 -5
- package/node_modules/@groove-dev/gui/src/views/memory.jsx +87 -44
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/journalist.js +52 -21
- package/packages/daemon/src/keeper.js +37 -2
- package/packages/daemon/src/routes/coordination.js +16 -0
- package/packages/daemon/src/routes/files.js +71 -2
- package/packages/gui/dist/assets/index-BTLb6zTD.js +1015 -0
- package/packages/gui/dist/assets/index-Diw6wDPU.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/agents/agent-feed.jsx +252 -44
- package/packages/gui/src/components/agents/agent-file-tree.jsx +51 -3
- package/packages/gui/src/components/editor/file-tree.jsx +40 -3
- package/packages/gui/src/stores/groove.js +9 -1
- package/packages/gui/src/stores/slices/agents-slice.js +24 -5
- package/packages/gui/src/views/memory.jsx +87 -44
- package/node_modules/@groove-dev/gui/dist/assets/index-BU_YTEZo.js +0 -1011
- package/node_modules/@groove-dev/gui/dist/assets/index-ChfYTsyc.css +0 -1
- package/packages/gui/dist/assets/index-BU_YTEZo.js +0 -1011
- package/packages/gui/dist/assets/index-ChfYTsyc.css +0 -1
|
@@ -95,12 +95,19 @@ export const createAgentsSlice = (set, get) => ({
|
|
|
95
95
|
|
|
96
96
|
// ── Chat ──────────────────────────────────────────────────
|
|
97
97
|
|
|
98
|
-
addChatMessage(agentId, from, text, isQuery = false) {
|
|
98
|
+
addChatMessage(agentId, from, text, isQuery = false, attachments = undefined) {
|
|
99
99
|
set((s) => {
|
|
100
100
|
const history = { ...s.chatHistory };
|
|
101
101
|
if (!history[agentId]) history[agentId] = [];
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
const msg = { from, text, timestamp: Date.now(), isQuery };
|
|
103
|
+
if (attachments?.length) msg.attachments = attachments;
|
|
104
|
+
history[agentId] = [...history[agentId].slice(-100), msg];
|
|
105
|
+
const forStorage = { ...history };
|
|
106
|
+
forStorage[agentId] = forStorage[agentId].map((m) => {
|
|
107
|
+
if (!m.attachments?.length) return m;
|
|
108
|
+
return { ...m, attachments: m.attachments.map(({ dataUrl, ...rest }) => rest) };
|
|
109
|
+
});
|
|
110
|
+
persistJSON('groove:chatHistory', forStorage);
|
|
104
111
|
return { chatHistory: history };
|
|
105
112
|
});
|
|
106
113
|
},
|
|
@@ -120,7 +127,7 @@ export const createAgentsSlice = (set, get) => ({
|
|
|
120
127
|
}
|
|
121
128
|
},
|
|
122
129
|
|
|
123
|
-
async instructAgent(id, message) {
|
|
130
|
+
async instructAgent(id, message, attachments = undefined) {
|
|
124
131
|
// ── Keeper command interception ─────────────────────────
|
|
125
132
|
const keeperCmd = message.match(/\[(save|append|update|delete|view|doc|link|read|instruct)\]/i);
|
|
126
133
|
if (keeperCmd) {
|
|
@@ -131,7 +138,7 @@ export const createAgentsSlice = (set, get) => ({
|
|
|
131
138
|
}
|
|
132
139
|
}
|
|
133
140
|
|
|
134
|
-
get().addChatMessage(id, 'user', message, false);
|
|
141
|
+
get().addChatMessage(id, 'user', message, false, attachments);
|
|
135
142
|
set((s) => ({ thinkingAgents: new Set([...s.thinkingAgents, id]) }));
|
|
136
143
|
|
|
137
144
|
// Auto-attach active file context when in workspace mode
|
|
@@ -332,6 +339,18 @@ export const createAgentsSlice = (set, get) => ({
|
|
|
332
339
|
}
|
|
333
340
|
},
|
|
334
341
|
|
|
342
|
+
async moveKeeperItem(oldTag, newTag) {
|
|
343
|
+
try {
|
|
344
|
+
const item = await api.post('/keeper/move', { oldTag, newTag });
|
|
345
|
+
get().fetchKeeperItems();
|
|
346
|
+
get().addToast('success', `Moved #${oldTag} → #${item.tag}`);
|
|
347
|
+
return item;
|
|
348
|
+
} catch (err) {
|
|
349
|
+
get().addToast('error', 'Failed to move memory', err.message);
|
|
350
|
+
throw err;
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
|
|
335
354
|
async getKeeperItem(tag) {
|
|
336
355
|
try {
|
|
337
356
|
return await api.get(`/keeper/${tag}`);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
-
import { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { useState, useEffect, useRef, useCallback } from 'react';
|
|
3
3
|
import { useGrooveStore } from '../stores/groove';
|
|
4
4
|
import { Button } from '../components/ui/button';
|
|
5
5
|
import { ScrollArea } from '../components/ui/scroll-area';
|
|
6
6
|
import { Dialog, DialogContent } from '../components/ui/dialog';
|
|
7
|
-
import { BookOpen, Plus, Search, Trash2, Pencil, ChevronRight, Hash, FolderOpen, Clock, Save, Link2, FileText, Sparkles, HelpCircle } from 'lucide-react';
|
|
7
|
+
import { BookOpen, Plus, Search, Trash2, Pencil, ChevronRight, Hash, FolderOpen, Clock, Save, Link2, FileText, Sparkles, HelpCircle, GripVertical } from 'lucide-react';
|
|
8
8
|
|
|
9
9
|
const COMMANDS = [
|
|
10
10
|
{ cmd: 'save', args: '#tag', desc: 'Save the message and send it to the agent' },
|
|
@@ -249,38 +249,71 @@ function InstructModal({ open, onOpenChange }) {
|
|
|
249
249
|
);
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
function
|
|
252
|
+
function TreeItem({ tag, label, isDoc, indent, isDragOver, onSelect, onDragStart, onDragOver, onDragLeave, onDrop }) {
|
|
253
|
+
return (
|
|
254
|
+
<div
|
|
255
|
+
draggable
|
|
256
|
+
onDragStart={(e) => { e.dataTransfer.setData('text/plain', tag); e.dataTransfer.effectAllowed = 'move'; onDragStart?.(tag); }}
|
|
257
|
+
onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; onDragOver?.(tag); }}
|
|
258
|
+
onDragLeave={() => onDragLeave?.()}
|
|
259
|
+
onDrop={(e) => { e.preventDefault(); onDrop?.(e.dataTransfer.getData('text/plain'), tag); }}
|
|
260
|
+
onClick={() => onSelect({ tag })}
|
|
261
|
+
className={`flex items-center gap-1.5 w-full px-2 py-1.5 rounded-md text-xs transition-colors cursor-pointer group ${isDragOver ? 'bg-accent/15 border border-accent/30 border-dashed' : 'hover:bg-surface-2'}`}
|
|
262
|
+
style={indent ? { paddingLeft: `${8 + indent * 16}px` } : undefined}
|
|
263
|
+
>
|
|
264
|
+
<GripVertical size={10} className="text-text-4 opacity-0 group-hover:opacity-50 flex-shrink-0 cursor-grab" />
|
|
265
|
+
<Hash size={11} className="text-text-4 flex-shrink-0" />
|
|
266
|
+
<span className="font-medium text-text-2 truncate">{label}</span>
|
|
267
|
+
{isDoc && <Sparkles size={9} className="text-purple flex-shrink-0" />}
|
|
268
|
+
</div>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function TreeGroup({ node, onSelect, dragOverTag, onDragStart, onDragOver, onDragLeave, onDrop }) {
|
|
253
273
|
const [expanded, setExpanded] = useState(true);
|
|
254
274
|
const hasChildren = node.children && node.children.length > 0;
|
|
255
275
|
|
|
276
|
+
if (!hasChildren) {
|
|
277
|
+
return (
|
|
278
|
+
<TreeItem
|
|
279
|
+
tag={node.tag} label={node.tag} isDoc={node.type === 'doc'}
|
|
280
|
+
isDragOver={dragOverTag === node.tag}
|
|
281
|
+
onSelect={onSelect} onDragStart={onDragStart} onDragOver={onDragOver} onDragLeave={onDragLeave} onDrop={onDrop}
|
|
282
|
+
/>
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
256
286
|
return (
|
|
257
|
-
<div
|
|
287
|
+
<div
|
|
288
|
+
onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; onDragOver?.(node.tag); }}
|
|
289
|
+
onDragLeave={() => onDragLeave?.()}
|
|
290
|
+
onDrop={(e) => { e.preventDefault(); onDrop?.(e.dataTransfer.getData('text/plain'), node.tag); }}
|
|
291
|
+
>
|
|
258
292
|
<button
|
|
259
|
-
onClick={() =>
|
|
260
|
-
className=
|
|
293
|
+
onClick={() => setExpanded(!expanded)}
|
|
294
|
+
className={`flex items-center gap-1.5 w-full px-2 py-1.5 rounded-md text-xs transition-colors cursor-pointer ${dragOverTag === node.tag ? 'bg-accent/15 border border-accent/30 border-dashed' : 'hover:bg-surface-2'}`}
|
|
261
295
|
>
|
|
262
|
-
{
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
<FolderOpen size={12} className={hasChildren ? 'text-accent' : 'text-text-4'} />
|
|
268
|
-
<span className="font-medium">{node.tag}</span>
|
|
269
|
-
{hasChildren && (
|
|
270
|
-
<span className="text-2xs text-text-4 ml-auto">{node.children.length}</span>
|
|
271
|
-
)}
|
|
296
|
+
<ChevronRight size={12} className={`transition-transform text-text-4 ${expanded ? 'rotate-90' : ''}`} />
|
|
297
|
+
<FolderOpen size={12} className="text-accent" />
|
|
298
|
+
<span className="font-medium text-text-1">{node.tag}</span>
|
|
299
|
+
{!node.virtual && node.type === 'doc' && <Sparkles size={9} className="text-purple" />}
|
|
300
|
+
<span className="text-2xs text-text-4 ml-auto">{node.children.length}</span>
|
|
272
301
|
</button>
|
|
273
|
-
{expanded &&
|
|
274
|
-
<div className="
|
|
302
|
+
{expanded && (
|
|
303
|
+
<div className="mt-0.5 space-y-0.5">
|
|
304
|
+
{!node.virtual && (
|
|
305
|
+
<TreeItem
|
|
306
|
+
tag={node.tag} label={node.tag} isDoc={node.type === 'doc'} indent={1}
|
|
307
|
+
isDragOver={false}
|
|
308
|
+
onSelect={onSelect} onDragStart={onDragStart} onDragOver={onDragOver} onDragLeave={onDragLeave} onDrop={onDrop}
|
|
309
|
+
/>
|
|
310
|
+
)}
|
|
275
311
|
{node.children.map((child) => (
|
|
276
|
-
<
|
|
277
|
-
key={child.tag}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
<Hash size={10} className="text-text-4" />
|
|
282
|
-
<span>{child.tag.split('/').pop()}</span>
|
|
283
|
-
</button>
|
|
312
|
+
<TreeItem
|
|
313
|
+
key={child.tag} tag={child.tag} label={child.tag.split('/').pop()} isDoc={child.type === 'doc'} indent={1}
|
|
314
|
+
isDragOver={dragOverTag === child.tag}
|
|
315
|
+
onSelect={onSelect} onDragStart={onDragStart} onDragOver={onDragOver} onDragLeave={onDragLeave} onDrop={onDrop}
|
|
316
|
+
/>
|
|
284
317
|
))}
|
|
285
318
|
</div>
|
|
286
319
|
)}
|
|
@@ -297,12 +330,15 @@ export default function MemoryView() {
|
|
|
297
330
|
const saveKeeperItem = useGrooveStore((s) => s.saveKeeperItem);
|
|
298
331
|
const updateKeeperItem = useGrooveStore((s) => s.updateKeeperItem);
|
|
299
332
|
const deleteKeeperItem = useGrooveStore((s) => s.deleteKeeperItem);
|
|
333
|
+
const moveKeeperItem = useGrooveStore((s) => s.moveKeeperItem);
|
|
300
334
|
const getKeeperItem = useGrooveStore((s) => s.getKeeperItem);
|
|
301
335
|
const setKeeperEditing = useGrooveStore((s) => s.setKeeperEditing);
|
|
302
336
|
|
|
303
337
|
const [search, setSearch] = useState('');
|
|
304
338
|
const [viewMode, setViewMode] = useState('list');
|
|
305
339
|
const [editorOpen, setEditorOpen] = useState(false);
|
|
340
|
+
const [dragOverTag, setDragOverTag] = useState(null);
|
|
341
|
+
const [draggingTag, setDraggingTag] = useState(null);
|
|
306
342
|
|
|
307
343
|
useEffect(() => { fetchKeeperItems(); }, []);
|
|
308
344
|
|
|
@@ -344,8 +380,22 @@ export default function MemoryView() {
|
|
|
344
380
|
await handleEdit(node);
|
|
345
381
|
};
|
|
346
382
|
|
|
383
|
+
const handleDrop = useCallback(async (sourceTag, targetTag) => {
|
|
384
|
+
setDragOverTag(null);
|
|
385
|
+
setDraggingTag(null);
|
|
386
|
+
if (!sourceTag || !targetTag || sourceTag === targetTag) return;
|
|
387
|
+
// Don't drop onto self or own children
|
|
388
|
+
if (targetTag.startsWith(sourceTag + '/')) return;
|
|
389
|
+
const sourceName = sourceTag.split('/').pop();
|
|
390
|
+
const newTag = targetTag + '/' + sourceName;
|
|
391
|
+
if (sourceTag === newTag) return;
|
|
392
|
+
try {
|
|
393
|
+
await moveKeeperItem(sourceTag, newTag);
|
|
394
|
+
} catch { /* toast handles */ }
|
|
395
|
+
}, [moveKeeperItem]);
|
|
396
|
+
|
|
347
397
|
return (
|
|
348
|
-
<div className="flex
|
|
398
|
+
<div className="flex flex-col h-full overflow-hidden">
|
|
349
399
|
{/* Header */}
|
|
350
400
|
<div className="flex-shrink-0 px-4 py-3 border-b border-border">
|
|
351
401
|
<div className="flex items-center justify-between gap-3 mb-3">
|
|
@@ -394,7 +444,7 @@ export default function MemoryView() {
|
|
|
394
444
|
</div>
|
|
395
445
|
|
|
396
446
|
{/* Content */}
|
|
397
|
-
<ScrollArea className="flex-1">
|
|
447
|
+
<ScrollArea className="flex-1 min-h-0">
|
|
398
448
|
{keeperItems.length === 0 ? (
|
|
399
449
|
<div className="flex flex-col items-center justify-center h-64 gap-3">
|
|
400
450
|
<BookOpen size={32} className="text-text-4" />
|
|
@@ -414,24 +464,17 @@ export default function MemoryView() {
|
|
|
414
464
|
</div>
|
|
415
465
|
</div>
|
|
416
466
|
) : viewMode === 'tree' ? (
|
|
417
|
-
<div className="p-3 space-y-
|
|
467
|
+
<div className="p-3 space-y-0.5" onDragOver={(e) => e.preventDefault()}>
|
|
418
468
|
{keeperTree.map((node) => (
|
|
419
|
-
<TreeGroup
|
|
469
|
+
<TreeGroup
|
|
470
|
+
key={node.tag} node={node} onSelect={handleTreeSelect}
|
|
471
|
+
dragOverTag={dragOverTag}
|
|
472
|
+
onDragStart={(tag) => setDraggingTag(tag)}
|
|
473
|
+
onDragOver={(tag) => { if (tag !== draggingTag) setDragOverTag(tag); }}
|
|
474
|
+
onDragLeave={() => setDragOverTag(null)}
|
|
475
|
+
onDrop={handleDrop}
|
|
476
|
+
/>
|
|
420
477
|
))}
|
|
421
|
-
{keeperItems
|
|
422
|
-
.filter((item) => !item.tag.includes('/') && !keeperTree.some((t) => t.tag === item.tag && t.children?.length))
|
|
423
|
-
.map((item) => (
|
|
424
|
-
<button
|
|
425
|
-
key={item.tag}
|
|
426
|
-
onClick={() => handleEdit(item)}
|
|
427
|
-
className="flex items-center gap-1.5 w-full px-2 py-1 rounded-md text-xs text-text-2 hover:bg-surface-2 transition-colors cursor-pointer"
|
|
428
|
-
>
|
|
429
|
-
<Hash size={12} className="text-text-4" />
|
|
430
|
-
<span className="font-medium">{item.tag}</span>
|
|
431
|
-
{item.type === 'doc' && <Sparkles size={10} className="text-purple" />}
|
|
432
|
-
<span className="text-2xs text-text-4 ml-auto">{formatRelative(item.updatedAt)}</span>
|
|
433
|
-
</button>
|
|
434
|
-
))}
|
|
435
478
|
</div>
|
|
436
479
|
) : (
|
|
437
480
|
<div className="p-3 space-y-2">
|