groove-dev 0.27.184 → 0.27.185
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/.watch-test-flag-1784767927904 +1 -0
- package/.watch-test-flag-1784768055729 +1 -0
- package/.watch-test-flag-1784768066364 +1 -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 +2 -0
- package/node_modules/@groove-dev/daemon/src/index.js +17 -0
- package/node_modules/@groove-dev/daemon/src/innerchat-docs.js +60 -5
- package/node_modules/@groove-dev/daemon/src/innerchat.js +167 -54
- package/node_modules/@groove-dev/daemon/src/introducer.js +3 -2
- package/node_modules/@groove-dev/daemon/src/process.js +19 -10
- package/node_modules/@groove-dev/daemon/src/routes/innerchat.js +80 -28
- package/node_modules/@groove-dev/daemon/src/routes/watch.js +42 -0
- package/node_modules/@groove-dev/daemon/src/watcher.js +258 -0
- package/node_modules/@groove-dev/daemon/test/innerchat.test.js +71 -1
- package/node_modules/@groove-dev/daemon/test/watcher.test.js +158 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-CU8L_r5f.css +1 -0
- package/node_modules/@groove-dev/gui/dist/assets/{index-BpOyN6Zf.js → index-DEZwM4Hb.js} +227 -222
- 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.css +1 -0
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +60 -4
- package/node_modules/@groove-dev/gui/src/stores/groove.js +30 -16
- package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +6 -0
- package/node_modules/@groove-dev/gui/src/views/memory.jsx +56 -31
- 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 +2 -0
- package/packages/daemon/src/index.js +17 -0
- package/packages/daemon/src/innerchat-docs.js +60 -5
- package/packages/daemon/src/innerchat.js +167 -54
- package/packages/daemon/src/introducer.js +3 -2
- package/packages/daemon/src/process.js +19 -10
- package/packages/daemon/src/routes/innerchat.js +80 -28
- package/packages/daemon/src/routes/watch.js +42 -0
- package/packages/daemon/src/watcher.js +258 -0
- package/packages/gui/dist/assets/index-CU8L_r5f.css +1 -0
- package/packages/gui/dist/assets/{index-BpOyN6Zf.js → index-DEZwM4Hb.js} +227 -222
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/app.css +1 -0
- package/packages/gui/src/components/agents/agent-feed.jsx +60 -4
- package/packages/gui/src/stores/groove.js +30 -16
- package/packages/gui/src/stores/slices/agents-slice.js +6 -0
- package/packages/gui/src/views/memory.jsx +56 -31
- package/node_modules/@groove-dev/gui/dist/assets/index-DiXB7yry.css +0 -1
- package/packages/gui/dist/assets/index-DiXB7yry.css +0 -1
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
8
8
|
<title>Groove GUI</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-DEZwM4Hb.js"></script>
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/vendor-26L3JoZv.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/reactflow-DoBZjiHE.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/codemirror-BYKpdS2W.js">
|
|
13
13
|
<link rel="modulepreload" crossorigin href="/assets/xterm--7_ns2zW.js">
|
|
14
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
14
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CU8L_r5f.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
17
17
|
<div id="root"></div>
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
Loader2, MessageSquare, SendHorizontal, Pause,
|
|
5
5
|
FileEdit, Search, Terminal, CheckCircle2, AlertCircle,
|
|
6
6
|
RotateCw, Zap, Wrench, Eye, Code2, Bug,
|
|
7
|
-
ChevronDown, Paperclip, GripHorizontal,
|
|
7
|
+
ChevronDown, Paperclip, GripHorizontal, ArrowLeftRight,
|
|
8
8
|
FileCode, X, File, Image as ImageIcon, Film, Upload,
|
|
9
9
|
} from 'lucide-react';
|
|
10
10
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
@@ -16,6 +16,7 @@ import { ThinkingIndicator } from '../ui/thinking-indicator';
|
|
|
16
16
|
import { TableTree } from '../ui/table-tree';
|
|
17
17
|
|
|
18
18
|
const EMPTY = [];
|
|
19
|
+
const EMPTY_MAP = {};
|
|
19
20
|
const KEEPER_RE = /(\[(?:save|append|update|delete|view|doc|link|read|instruct)\]|#[\w/.-]+)/gi;
|
|
20
21
|
const KEEPER_CMD_RE = /^\[(?:save|append|update|delete|view|doc|link|read|instruct)\]$/i;
|
|
21
22
|
const KEEPER_TAG_RE = /^#[\w/.-]+$/;
|
|
@@ -339,7 +340,45 @@ function UserMessage({ msg }) {
|
|
|
339
340
|
);
|
|
340
341
|
}
|
|
341
342
|
|
|
342
|
-
|
|
343
|
+
// An agent-to-agent turn. Deliberately styled apart from the normal feed —
|
|
344
|
+
// amber rail and an explicit "X → Y" header — so it reads as traffic between
|
|
345
|
+
// agents rather than something addressed to the user.
|
|
346
|
+
function InnerChatMessage({ msg, agent }) {
|
|
347
|
+
const [collapsed, setCollapsed] = useState(msg.text?.length > 400);
|
|
348
|
+
const isLong = msg.text?.length > 400;
|
|
349
|
+
const { peer, direction, kind } = msg.innerchat || {};
|
|
350
|
+
const outbound = direction === 'out';
|
|
351
|
+
const me = agent?.name || 'this agent';
|
|
352
|
+
|
|
353
|
+
return (
|
|
354
|
+
<div className="rounded-md bg-indigo/[0.04] border border-indigo/15 px-2.5 py-2">
|
|
355
|
+
<div className="flex items-center gap-1.5 mb-1">
|
|
356
|
+
<ArrowLeftRight size={10} className="text-indigo flex-shrink-0" />
|
|
357
|
+
<span className="text-2xs font-semibold text-indigo font-sans">InnerChat</span>
|
|
358
|
+
<span className="text-2xs text-text-3 font-sans truncate">
|
|
359
|
+
{outbound ? `${me} → ${peer?.name}` : `${peer?.name} → ${me}`}
|
|
360
|
+
</span>
|
|
361
|
+
<span className="text-2xs text-text-4 font-sans">
|
|
362
|
+
{kind === 'answer' ? 'answer' : outbound ? 'asked' : 'asks'}
|
|
363
|
+
</span>
|
|
364
|
+
<span className="text-[10px] text-text-4 font-sans ml-auto flex-shrink-0">{timeAgo(msg.timestamp)}</span>
|
|
365
|
+
</div>
|
|
366
|
+
<div className="border-l-2 border-indigo/40 pl-3">
|
|
367
|
+
<StructuredMessage text={collapsed ? msg.text.slice(0, 400) + '...' : msg.text} />
|
|
368
|
+
</div>
|
|
369
|
+
{isLong && (
|
|
370
|
+
<button
|
|
371
|
+
onClick={() => setCollapsed((c) => !c)}
|
|
372
|
+
className="mt-1 text-2xs text-text-4 hover:text-indigo font-sans cursor-pointer"
|
|
373
|
+
>
|
|
374
|
+
{collapsed ? 'Show more' : 'Show less'}
|
|
375
|
+
</button>
|
|
376
|
+
)}
|
|
377
|
+
</div>
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function AgentMessage({ msg, agent, answeredTo }) {
|
|
343
382
|
const [collapsed, setCollapsed] = useState(msg.text?.length > 600);
|
|
344
383
|
const isLong = msg.text?.length > 600;
|
|
345
384
|
|
|
@@ -348,9 +387,16 @@ function AgentMessage({ msg, agent }) {
|
|
|
348
387
|
<div className="flex items-center gap-2 mb-1">
|
|
349
388
|
<span className="text-2xs font-semibold text-text-1 font-sans">{agent?.name || 'Agent'}</span>
|
|
350
389
|
<span className="text-2xs text-text-4 font-sans">{agent?.role}</span>
|
|
390
|
+
{/* This reply went to another agent, not to the user */}
|
|
391
|
+
{answeredTo && (
|
|
392
|
+
<span className="flex items-center gap-1 px-1.5 py-px rounded bg-indigo/10 text-2xs font-medium text-indigo font-sans">
|
|
393
|
+
<ArrowLeftRight size={9} />
|
|
394
|
+
to {answeredTo.name}
|
|
395
|
+
</span>
|
|
396
|
+
)}
|
|
351
397
|
<span className="text-[10px] text-text-4 font-sans ml-auto">{timeAgo(msg.timestamp)}</span>
|
|
352
398
|
</div>
|
|
353
|
-
<div className=
|
|
399
|
+
<div className={cn('pl-3.5 py-1 border-l', answeredTo ? 'border-indigo/50' : 'border-accent')}>
|
|
354
400
|
<StructuredMessage text={collapsed ? msg.text.slice(0, 600) + '...' : msg.text} />
|
|
355
401
|
</div>
|
|
356
402
|
{collapsed && (
|
|
@@ -606,6 +652,8 @@ export function AgentFeed({ agent, readOnly = false }) {
|
|
|
606
652
|
const rawActivityLog = useGrooveStore((s) => s.activityLog[agent.id]) || EMPTY;
|
|
607
653
|
const instructAgent = useGrooveStore((s) => s.instructAgent);
|
|
608
654
|
const isThinking = useGrooveStore((s) => s.thinkingAgents?.has(agent.id));
|
|
655
|
+
// Replies this agent sent to other agents, keyed by text — see innerchatAnswers.
|
|
656
|
+
const answerPeers = useGrooveStore((s) => s.innerchatAnswers?.[agent.id]) || EMPTY_MAP;
|
|
609
657
|
const cachedChatRef = useRef(EMPTY);
|
|
610
658
|
const cachedActivityRef = useRef(EMPTY);
|
|
611
659
|
const cachedAgentIdRef = useRef(agent.id);
|
|
@@ -886,7 +934,15 @@ export function AgentFeed({ agent, readOnly = false }) {
|
|
|
886
934
|
}
|
|
887
935
|
if (item.from === 'user') return <UserMessage key={`user-${item._chatIdx}`} msg={item} />;
|
|
888
936
|
if (item.from === 'system') return <SystemMessage key={`sys-${item._chatIdx}`} msg={item} />;
|
|
889
|
-
return <
|
|
937
|
+
if (item.from === 'innerchat') return <InnerChatMessage key={`ic-${item._chatIdx}`} msg={item} agent={agent} />;
|
|
938
|
+
return (
|
|
939
|
+
<AgentMessage
|
|
940
|
+
key={`agent-${item._chatIdx}`}
|
|
941
|
+
msg={item}
|
|
942
|
+
agent={agent}
|
|
943
|
+
answeredTo={answerPeers[item.text?.trim()]?.peer}
|
|
944
|
+
/>
|
|
945
|
+
);
|
|
890
946
|
})}
|
|
891
947
|
<AnimatePresence>
|
|
892
948
|
{(sending || isThinking) && (
|
|
@@ -372,36 +372,50 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
372
372
|
case 'innerchat:turn': {
|
|
373
373
|
const { thread, turn } = msg.data;
|
|
374
374
|
if (turn.status === 'failed') {
|
|
375
|
-
get().addToast('error', `
|
|
375
|
+
get().addToast('error', `InnerChat to ${turn.to.name} failed`, turn.error);
|
|
376
|
+
break;
|
|
376
377
|
}
|
|
377
378
|
|
|
378
|
-
// The turn lands in the recipient's chat, badged with who sent it.
|
|
379
379
|
set((s) => {
|
|
380
380
|
const history = { ...s.chatHistory };
|
|
381
|
-
const
|
|
382
|
-
arr.push({
|
|
381
|
+
const entry = (peer, direction) => ({
|
|
383
382
|
from: 'innerchat',
|
|
384
383
|
text: turn.text,
|
|
385
384
|
timestamp: turn.timestamp,
|
|
386
385
|
isQuery: false,
|
|
387
|
-
innerchat: {
|
|
388
|
-
turnId: turn.id,
|
|
389
|
-
threadId: thread.id,
|
|
390
|
-
kind: turn.kind,
|
|
391
|
-
fromAgent: turn.from,
|
|
392
|
-
},
|
|
386
|
+
innerchat: { turnId: turn.id, threadId: thread.id, kind: turn.kind, peer, direction },
|
|
393
387
|
});
|
|
394
|
-
|
|
388
|
+
const push = (agentId, item) => {
|
|
389
|
+
history[agentId] = [...(history[agentId] || []), item].slice(-100);
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
if (turn.kind === 'ask') {
|
|
393
|
+
// Both sides see the question — the asker as outgoing, the
|
|
394
|
+
// target as incoming — so neither feed has an unexplained gap.
|
|
395
|
+
push(turn.from.id, entry(turn.to, 'out'));
|
|
396
|
+
push(turn.to.id, entry(turn.from, 'in'));
|
|
397
|
+
} else {
|
|
398
|
+
// The answer reaches the asker over HTTP, so only it needs a
|
|
399
|
+
// feed entry. The responder already shows this text as its own
|
|
400
|
+
// agent message; that one gets badged via innerchatAnswers
|
|
401
|
+
// rather than duplicated here.
|
|
402
|
+
push(turn.to.id, entry(turn.from, 'in'));
|
|
403
|
+
}
|
|
404
|
+
|
|
395
405
|
persistJSON('groove:chatHistory', history);
|
|
396
406
|
|
|
407
|
+
const answers = { ...s.innerchatAnswers };
|
|
408
|
+
if (turn.kind === 'answer') {
|
|
409
|
+
answers[turn.from.id] = {
|
|
410
|
+
...(answers[turn.from.id] || {}),
|
|
411
|
+
[turn.text.trim()]: { peer: turn.to, threadId: thread.id },
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
397
415
|
const threads = { ...s.innerchatThreads, [thread.id]: thread };
|
|
398
416
|
persistJSON('groove:innerchatThreads', threads);
|
|
399
|
-
return { chatHistory: history, innerchatThreads: threads };
|
|
417
|
+
return { chatHistory: history, innerchatThreads: threads, innerchatAnswers: answers };
|
|
400
418
|
});
|
|
401
|
-
|
|
402
|
-
if (turn.kind === 'reply' && turn.status !== 'failed') {
|
|
403
|
-
get().addToast('info', `${turn.from.name} replied`, `Forwarded to ${turn.to.name}`);
|
|
404
|
-
}
|
|
405
419
|
break;
|
|
406
420
|
}
|
|
407
421
|
|
|
@@ -123,6 +123,12 @@ export const createAgentsSlice = (set, get) => ({
|
|
|
123
123
|
|
|
124
124
|
innerchatThreads: loadJSON('groove:innerchatThreads') || {},
|
|
125
125
|
|
|
126
|
+
// agentId -> { <answer text>: { peer, threadId } }. An agent's answer to
|
|
127
|
+
// another agent arrives as an ordinary result, indistinguishable in its own
|
|
128
|
+
// feed from a message meant for the user — this is what lets the feed badge
|
|
129
|
+
// it. Keyed by text so it works regardless of which event lands first.
|
|
130
|
+
innerchatAnswers: {},
|
|
131
|
+
|
|
126
132
|
// Relay a message from one agent to another. Pass threadId to continue an
|
|
127
133
|
// existing exchange rather than opening a new one.
|
|
128
134
|
async sendInnerChat(fromId, toId, message, threadId = null) {
|
|
@@ -6,16 +6,19 @@ import { ScrollArea } from '../components/ui/scroll-area';
|
|
|
6
6
|
import { Dialog, DialogContent } from '../components/ui/dialog';
|
|
7
7
|
import { BookOpen, Plus, Search, Trash2, Pencil, ChevronRight, Hash, FolderOpen, Clock, Save, Link2, FileText, Sparkles, HelpCircle, GripVertical, CornerLeftUp } from 'lucide-react';
|
|
8
8
|
|
|
9
|
+
// `cmd` is the bare word; the UI always renders it bracketed so the real
|
|
10
|
+
// syntax — [cmd] #tag — is never ambiguous. `example` shows a full, copyable
|
|
11
|
+
// line so a new user can see exactly what to type.
|
|
9
12
|
const COMMANDS = [
|
|
10
|
-
{ cmd: 'save',
|
|
11
|
-
{ cmd: 'append',
|
|
12
|
-
{ cmd: 'update',
|
|
13
|
-
{ cmd: '
|
|
14
|
-
{ cmd: '
|
|
15
|
-
{ cmd: '
|
|
16
|
-
{ cmd: '
|
|
17
|
-
{ cmd: '
|
|
18
|
-
{ cmd: '
|
|
13
|
+
{ cmd: 'save', example: '[save] #api-auth', desc: 'Save the current message as a new memory under #api-auth' },
|
|
14
|
+
{ cmd: 'append', example: '[append] #api-auth', desc: 'Add the current message to the existing #api-auth memory' },
|
|
15
|
+
{ cmd: 'update', example: '[update] #api-auth', desc: 'Open the editor to change the #api-auth memory' },
|
|
16
|
+
{ cmd: 'view', example: '[view] #api-auth', desc: 'Open a memory to read it, without sending it to the agent' },
|
|
17
|
+
{ cmd: 'read', example: '[read] #api-auth #db-schema', desc: 'Send one or more memories to the agent as context (chat stays clean)' },
|
|
18
|
+
{ cmd: 'doc', example: '[doc] #api-auth', desc: 'Let the AI synthesize the conversation into a memory' },
|
|
19
|
+
{ cmd: 'link', example: '[link] #api-auth docs/spec.md', desc: 'Link a memory to a NORTHSTAR or external document' },
|
|
20
|
+
{ cmd: 'delete', example: '[delete] #api-auth', desc: 'Delete a memory permanently' },
|
|
21
|
+
{ cmd: 'instruct', example: '[instruct]', desc: 'Show this command reference' },
|
|
19
22
|
];
|
|
20
23
|
|
|
21
24
|
function formatRelative(iso) {
|
|
@@ -219,36 +222,58 @@ function EditorModal({ open, onOpenChange, editing, onSave, onRename }) {
|
|
|
219
222
|
function InstructModal({ open, onOpenChange }) {
|
|
220
223
|
return (
|
|
221
224
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
222
|
-
<DialogContent title="
|
|
225
|
+
<DialogContent title="Memory Commands" description="How to save and recall tagged memories" className="max-w-lg">
|
|
223
226
|
<div className="p-5 space-y-4">
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
227
|
+
{/* Format primer — the part new users miss */}
|
|
228
|
+
<div className="rounded-lg bg-surface-2 border border-border-subtle p-3 space-y-2">
|
|
229
|
+
<p className="text-xs text-text-2 leading-relaxed">
|
|
230
|
+
Type a command in any agent chat. The format is always the command in
|
|
231
|
+
<span className="font-mono text-accent"> [square brackets]</span>, then a
|
|
232
|
+
<span className="font-mono text-accent"> #tag</span>:
|
|
233
|
+
</p>
|
|
234
|
+
<div className="flex items-center gap-2 font-mono text-sm">
|
|
235
|
+
<span className="px-1.5 py-0.5 rounded bg-accent/15 text-accent font-semibold">[save]</span>
|
|
236
|
+
<span className="px-1.5 py-0.5 rounded bg-info/10 text-info font-semibold">#api-auth</span>
|
|
237
|
+
<span className="text-text-3 text-xs font-sans">your message here</span>
|
|
238
|
+
</div>
|
|
239
|
+
<p className="text-xs text-text-4 leading-relaxed">
|
|
240
|
+
These commands are handled by GROOVE — the agent never sees them.
|
|
241
|
+
</p>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
<div className="space-y-1">
|
|
228
245
|
{COMMANDS.map((c) => (
|
|
229
246
|
<div key={c.cmd} className="flex items-start gap-3 py-1.5 border-b border-border-subtle last:border-0">
|
|
230
|
-
<
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
247
|
+
<code className="flex-shrink-0 w-44 text-xs font-mono text-text-1 pt-0.5">
|
|
248
|
+
{c.example.split(/(\[[^\]]+\]|#[\w/-]+)/).map((part, i) => {
|
|
249
|
+
if (/^\[.+\]$/.test(part)) return <span key={i} className="text-accent font-semibold">{part}</span>;
|
|
250
|
+
if (/^#/.test(part)) return <span key={i} className="text-info">{part}</span>;
|
|
251
|
+
return <span key={i} className="text-text-4">{part}</span>;
|
|
252
|
+
})}
|
|
253
|
+
</code>
|
|
234
254
|
<span className="text-xs text-text-3 pt-0.5">{c.desc}</span>
|
|
235
255
|
</div>
|
|
236
256
|
))}
|
|
237
257
|
</div>
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
<
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
<
|
|
249
|
-
|
|
250
|
-
|
|
258
|
+
|
|
259
|
+
<div className="pt-1 space-y-2.5">
|
|
260
|
+
<div>
|
|
261
|
+
<h3 className="text-xs font-semibold text-text-1 mb-1">Nested tags</h3>
|
|
262
|
+
<p className="text-xs text-text-3 leading-relaxed">
|
|
263
|
+
Use <span className="font-mono text-accent">/</span> to nest:
|
|
264
|
+
<span className="font-mono text-info"> #groove/memory</span> lives under
|
|
265
|
+
<span className="font-mono text-info"> #groove</span>. Reading the parent tag pulls every memory beneath it.
|
|
266
|
+
</p>
|
|
267
|
+
</div>
|
|
268
|
+
<div>
|
|
269
|
+
<h3 className="text-xs font-semibold text-text-1 mb-1">Give a new agent context</h3>
|
|
270
|
+
<p className="text-xs text-text-3 leading-relaxed">
|
|
271
|
+
Use <span className="font-mono"><span className="text-accent">[read]</span> <span className="text-info">#tag</span></span> to
|
|
272
|
+
drop saved memories into an agent's chat as background — useful right after spawning one.
|
|
273
|
+
</p>
|
|
274
|
+
</div>
|
|
251
275
|
</div>
|
|
276
|
+
|
|
252
277
|
<div className="flex justify-end pt-1">
|
|
253
278
|
<Button variant="ghost" size="sm" onClick={() => onOpenChange(false)}>Close</Button>
|
|
254
279
|
</div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.185",
|
|
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)",
|
|
@@ -28,6 +28,7 @@ import { registerFileRoutes, resetEditorRoot } from './routes/files.js';
|
|
|
28
28
|
import { registerNetworkRoutes } from './routes/network.js';
|
|
29
29
|
import { registerScheduleRoutes } from './routes/schedules.js';
|
|
30
30
|
import { registerInnerChatRoutes } from './routes/innerchat.js';
|
|
31
|
+
import { registerWatchRoutes } from './routes/watch.js';
|
|
31
32
|
import { registerAutoAgentRoutes } from './routes/auto-agents.js';
|
|
32
33
|
|
|
33
34
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -178,6 +179,7 @@ export function createApi(app, daemon) {
|
|
|
178
179
|
registerNetworkRoutes(app, daemon);
|
|
179
180
|
registerScheduleRoutes(app, daemon);
|
|
180
181
|
registerInnerChatRoutes(app, daemon);
|
|
182
|
+
registerWatchRoutes(app, daemon);
|
|
181
183
|
registerAutoAgentRoutes(app, daemon);
|
|
182
184
|
|
|
183
185
|
|
|
@@ -47,6 +47,7 @@ import { RepoImporter } from './repo-import.js';
|
|
|
47
47
|
import { ConversationManager } from './conversations.js';
|
|
48
48
|
import { Toys } from './toys.js';
|
|
49
49
|
import { InnerChat } from './innerchat.js';
|
|
50
|
+
import { Watcher } from './watcher.js';
|
|
50
51
|
import { AutoState } from './autostate.js';
|
|
51
52
|
import { Orchestrator } from './orchestrator.js';
|
|
52
53
|
import { TrajectoryCapture, ConsentManager } from '../../../moe-training/client/index.js';
|
|
@@ -162,6 +163,7 @@ export class Daemon {
|
|
|
162
163
|
this.modelLab = new ModelLab(this);
|
|
163
164
|
this.toys = new Toys(this);
|
|
164
165
|
this.innerchat = new InnerChat(this);
|
|
166
|
+
this.watcher = new Watcher(this);
|
|
165
167
|
this.autoState = new AutoState(this.grooveDir);
|
|
166
168
|
this.orchestrator = new Orchestrator(this);
|
|
167
169
|
this.trajectoryCapture = null;
|
|
@@ -623,6 +625,19 @@ export class Daemon {
|
|
|
623
625
|
this.federation.initialize();
|
|
624
626
|
this._startGarbageCollector();
|
|
625
627
|
|
|
628
|
+
// Regenerate the on-disk registry files once on boot. They otherwise
|
|
629
|
+
// only refresh on a registry change, so a daemon upgraded with new
|
|
630
|
+
// agent-facing docs (InnerChat, Watch) would serve stale AGENTS_REGISTRY.md
|
|
631
|
+
// to already-running agents until something happened to change state.
|
|
632
|
+
if (this.registry.getAll().length > 0) {
|
|
633
|
+
try {
|
|
634
|
+
this.introducer.writeRegistryFile(this.projectDir);
|
|
635
|
+
this.introducer.injectGrooveSection(this.projectDir);
|
|
636
|
+
} catch (err) {
|
|
637
|
+
console.error('[startup] Failed to refresh registry files:', err.message);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
626
641
|
// Restore auth token from stored config so subscription polling works after restart
|
|
627
642
|
const storedToken = this.skills.getToken();
|
|
628
643
|
if (storedToken) {
|
|
@@ -847,6 +862,8 @@ export class Daemon {
|
|
|
847
862
|
this.journalist.stop();
|
|
848
863
|
this.rotator.stop();
|
|
849
864
|
this.scheduler.stop();
|
|
865
|
+
this.watcher.stop();
|
|
866
|
+
this.innerchat.stop();
|
|
850
867
|
this.orchestrator.stop();
|
|
851
868
|
this.timeline.stop();
|
|
852
869
|
if (this._gcInterval) clearInterval(this._gcInterval);
|
|
@@ -17,25 +17,80 @@ export function innerChatInstructions(port = 31415, agentName = 'YOUR_NAME') {
|
|
|
17
17
|
'When the user asks you to reach out to, coordinate with, or get input from',
|
|
18
18
|
'another agent, use this — it sends them a message and waits for their reply:',
|
|
19
19
|
'',
|
|
20
|
+
'> **Do NOT use your built-in `SendMessage` / Agent tools to reach a GROOVE agent.**',
|
|
21
|
+
'> Those only address sub-agents you spawned yourself in this session, so they will',
|
|
22
|
+
'> fail with "not reachable" or ask you for an `a…-…` agent ID that does not exist',
|
|
23
|
+
'> here. GROOVE agents are separate processes. The curl below is the only way to',
|
|
24
|
+
'> reach them. Do not invent a fallback (writing a message into a file, etc.) —',
|
|
25
|
+
'> if the curl fails, read the error and report it to the user.',
|
|
26
|
+
'',
|
|
20
27
|
'```bash',
|
|
21
28
|
`curl -s http://localhost:${port}/api/innerchat/ask -X POST -H 'Content-Type: application/json' \\`,
|
|
22
29
|
` -d '{"from":"${agentName}","to":"AGENT_NAME","message":"YOUR_QUESTION"}'`,
|
|
23
30
|
'```',
|
|
24
31
|
'',
|
|
25
|
-
'
|
|
32
|
+
'This BLOCKS until they answer, then returns:',
|
|
26
33
|
'`{"from":"...","reply":"...","exchangesUsed":N,"exchangesRemaining":N}`',
|
|
27
34
|
'',
|
|
35
|
+
'> **Run the curl in the FOREGROUND and let it block.** Do not background it with',
|
|
36
|
+
"> `&`, and never put `&` inside the command — that detaches the curl and throws away",
|
|
37
|
+
"> the reply, so you get no answer and can't confirm delivery. Blocking for a minute or",
|
|
38
|
+
'> two is expected and correct; just wait for it. If you truly cannot wait, use `tell`',
|
|
39
|
+
'> (below) instead — that is the right tool for fire-and-forget, not a backgrounded ask.',
|
|
40
|
+
'',
|
|
28
41
|
'Call it again to continue — prior turns are included automatically, so just say',
|
|
29
42
|
'the next thing. Keep going until you reach a conclusion together, then report',
|
|
30
|
-
'that conclusion to the user.',
|
|
43
|
+
'that conclusion to the user. `ask` is best for tight back-and-forth (negotiating',
|
|
44
|
+
'an interface, resolving a blocker) where you need the answer before you proceed.',
|
|
45
|
+
'',
|
|
46
|
+
'If the other agent is heads-down and you do NOT need the answer right now, send it',
|
|
47
|
+
'without blocking instead — this returns immediately, and their reply (if any) is',
|
|
48
|
+
'delivered back to you later, waking you if your turn has ended:',
|
|
49
|
+
'',
|
|
50
|
+
'```bash',
|
|
51
|
+
`curl -s http://localhost:${port}/api/innerchat/tell -X POST -H 'Content-Type: application/json' \\`,
|
|
52
|
+
` -d '{"from":"${agentName}","to":"AGENT_NAME","message":"YOUR_MESSAGE"}'`,
|
|
53
|
+
'```',
|
|
31
54
|
'',
|
|
32
55
|
'**Rules:**',
|
|
33
56
|
'- Only start a conversation when the user asks you to. Never consult other agents on your own initiative.',
|
|
34
57
|
`- \`from\` must be your own agent name${agentName === 'YOUR_NAME' ? ' (it is in your $GROOVE_AGENT_NAME environment variable)' : ''}.`,
|
|
35
58
|
'- Agent names are listed in `AGENTS_REGISTRY.md`, including agents on other teams.',
|
|
36
|
-
'-
|
|
59
|
+
'- Use `ask` when you need the reply now; use `tell` to hand off or notify without waiting.',
|
|
60
|
+
'- One message per call. With `ask`, wait for the answer before deciding what to ask next.',
|
|
61
|
+
'- Ground truth lives in files (specs, code, run output), not in chat — use InnerChat to',
|
|
62
|
+
' negotiate and hand off, and verify claims against the actual artifacts.',
|
|
37
63
|
'- Read any error you get back — it tells you what to do (unknown name, cap reached, or they are waiting on you).',
|
|
38
|
-
'- `exchangesRemaining` counts down. At 0 the conversation is over: stop and report
|
|
39
|
-
'- If another agent asks YOU something, answer it directly in your next message
|
|
64
|
+
'- `exchangesRemaining` counts down (shared by ask and tell). At 0 the conversation is over: stop and report.',
|
|
65
|
+
'- If another agent asks YOU something (a blocking ask), answer it directly in your next message — they are blocked until you do.',
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Wake-on-completion. Same delivery/injection story as InnerChat above.
|
|
70
|
+
export function watchInstructions(port = 31415, agentName = 'YOUR_NAME') {
|
|
71
|
+
const nameHint = agentName === 'YOUR_NAME' ? ' ($GROOVE_AGENT_NAME)' : '';
|
|
72
|
+
return [
|
|
73
|
+
'## Getting Notified When Something Finishes (Watch)',
|
|
74
|
+
'',
|
|
75
|
+
'Your process ends when your turn ends, so a test suite or long job you kicked',
|
|
76
|
+
'off has nothing to notify you — telling the user "I\'ll report back when it lands"',
|
|
77
|
+
'does not work on its own; the session is gone. Instead, set a Watch. The daemon',
|
|
78
|
+
'outlives your turn and RESUMES you with the result when it finishes:',
|
|
79
|
+
'',
|
|
80
|
+
'```bash',
|
|
81
|
+
`curl -s http://localhost:${port}/api/watch -X POST -H 'Content-Type: application/json' \\`,
|
|
82
|
+
` -d '{"agent":"${agentName}","label":"test suite","command":"npm test"}'`,
|
|
83
|
+
'```',
|
|
84
|
+
'',
|
|
85
|
+
'This returns immediately with a `watchId`. **End your turn** — you will be woken',
|
|
86
|
+
'with the exit code and output tail when the command finishes.',
|
|
87
|
+
'',
|
|
88
|
+
'- Prefer `command` (let the daemon run it) over launching it yourself — that way',
|
|
89
|
+
' it captures the real exit code and output.',
|
|
90
|
+
'- For something ALREADY running, poll instead with `until` (fires when the check',
|
|
91
|
+
` exits 0): \`-d '{"agent":"${agentName}","label":"server up","until":"curl -sf localhost:3000/health"}'\`.`,
|
|
92
|
+
`- \`agent\` must be your own name${nameHint}.`,
|
|
93
|
+
'- Watches time out (default 30 min). One clear task per watch; do not watch trivial commands you could just run.',
|
|
94
|
+
'- Do not poll or sleep-loop waiting yourself — set the watch and end the turn. That is the whole point.',
|
|
40
95
|
];
|
|
41
96
|
}
|