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.
Files changed (48) hide show
  1. package/.watch-test-flag-1784767927904 +1 -0
  2. package/.watch-test-flag-1784768055729 +1 -0
  3. package/.watch-test-flag-1784768066364 +1 -0
  4. package/node_modules/@groove-dev/cli/package.json +1 -1
  5. package/node_modules/@groove-dev/daemon/package.json +1 -1
  6. package/node_modules/@groove-dev/daemon/src/api.js +2 -0
  7. package/node_modules/@groove-dev/daemon/src/index.js +17 -0
  8. package/node_modules/@groove-dev/daemon/src/innerchat-docs.js +60 -5
  9. package/node_modules/@groove-dev/daemon/src/innerchat.js +167 -54
  10. package/node_modules/@groove-dev/daemon/src/introducer.js +3 -2
  11. package/node_modules/@groove-dev/daemon/src/process.js +19 -10
  12. package/node_modules/@groove-dev/daemon/src/routes/innerchat.js +80 -28
  13. package/node_modules/@groove-dev/daemon/src/routes/watch.js +42 -0
  14. package/node_modules/@groove-dev/daemon/src/watcher.js +258 -0
  15. package/node_modules/@groove-dev/daemon/test/innerchat.test.js +71 -1
  16. package/node_modules/@groove-dev/daemon/test/watcher.test.js +158 -0
  17. package/node_modules/@groove-dev/gui/dist/assets/index-CU8L_r5f.css +1 -0
  18. package/node_modules/@groove-dev/gui/dist/assets/{index-BpOyN6Zf.js → index-DEZwM4Hb.js} +227 -222
  19. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  20. package/node_modules/@groove-dev/gui/package.json +1 -1
  21. package/node_modules/@groove-dev/gui/src/app.css +1 -0
  22. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +60 -4
  23. package/node_modules/@groove-dev/gui/src/stores/groove.js +30 -16
  24. package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +6 -0
  25. package/node_modules/@groove-dev/gui/src/views/memory.jsx +56 -31
  26. package/package.json +1 -1
  27. package/packages/cli/package.json +1 -1
  28. package/packages/daemon/package.json +1 -1
  29. package/packages/daemon/src/api.js +2 -0
  30. package/packages/daemon/src/index.js +17 -0
  31. package/packages/daemon/src/innerchat-docs.js +60 -5
  32. package/packages/daemon/src/innerchat.js +167 -54
  33. package/packages/daemon/src/introducer.js +3 -2
  34. package/packages/daemon/src/process.js +19 -10
  35. package/packages/daemon/src/routes/innerchat.js +80 -28
  36. package/packages/daemon/src/routes/watch.js +42 -0
  37. package/packages/daemon/src/watcher.js +258 -0
  38. package/packages/gui/dist/assets/index-CU8L_r5f.css +1 -0
  39. package/packages/gui/dist/assets/{index-BpOyN6Zf.js → index-DEZwM4Hb.js} +227 -222
  40. package/packages/gui/dist/index.html +2 -2
  41. package/packages/gui/package.json +1 -1
  42. package/packages/gui/src/app.css +1 -0
  43. package/packages/gui/src/components/agents/agent-feed.jsx +60 -4
  44. package/packages/gui/src/stores/groove.js +30 -16
  45. package/packages/gui/src/stores/slices/agents-slice.js +6 -0
  46. package/packages/gui/src/views/memory.jsx +56 -31
  47. package/node_modules/@groove-dev/gui/dist/assets/index-DiXB7yry.css +0 -1
  48. 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-BpOyN6Zf.js"></script>
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-DiXB7yry.css">
14
+ <link rel="stylesheet" crossorigin href="/assets/index-CU8L_r5f.css">
15
15
  </head>
16
16
  <body>
17
17
  <div id="root"></div>
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/gui",
3
- "version": "0.27.184",
3
+ "version": "0.27.185",
4
4
  "description": "GROOVE GUI — visual agent control plane",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -40,6 +40,7 @@
40
40
  --color-info: #7ab0df;
41
41
  --color-purple: #b07fd5;
42
42
  --color-orange: #c4956a;
43
+ --color-indigo: #7c86e0; /* InnerChat — agent-to-agent traffic */
43
44
 
44
45
  /* Fonts */
45
46
  --font-sans: 'Inter Variable', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
@@ -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
- function AgentMessage({ msg, agent }) {
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="border-l border-accent pl-3.5 py-1">
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 <AgentMessage key={`agent-${item._chatIdx}`} msg={item} agent={agent} />;
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', `Relay to ${turn.to.name} failed`, turn.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 arr = [...(history[turn.to.id] || [])];
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
- history[turn.to.id] = arr.slice(-100);
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', args: '#tag', desc: 'Save the message and send it to the agent' },
11
- { cmd: 'append', args: '#tag', desc: 'Add to an existing memory and send to agent' },
12
- { cmd: 'update', args: '#tag', desc: 'Open the editor to modify a memory in place' },
13
- { cmd: 'delete', args: '#tag', desc: 'Remove a memory permanently' },
14
- { cmd: 'view', args: '#tag', desc: 'Read a memory in the viewer' },
15
- { cmd: 'read', args: '#tag1 #tag2 ...', desc: 'Send memory content to the agent chat stays clean' },
16
- { cmd: 'doc', args: '#tag', desc: 'AI synthesizes the full conversation into a document' },
17
- { cmd: 'link', args: '#tag path/to/doc', desc: 'Link a memory to a NORTHSTAR or external document' },
18
- { cmd: '[instruct]', args: '', desc: 'Show this command reference' },
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="Keeper Commands" description="Memory system command reference" className="max-w-lg">
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
- <p className="text-xs text-text-2 leading-relaxed">
225
- Type these commands in any agent chat to manage your tagged memories. Commands are intercepted by the Keeper sidecar — the agent never sees them.
226
- </p>
227
- <div className="space-y-1.5">
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
- <div className="flex-shrink-0 flex items-center gap-1">
231
- <span className="px-1.5 py-0.5 rounded bg-accent/15 text-accent font-mono text-xs font-semibold">{c.cmd}</span>
232
- {c.args && <span className="text-xs text-text-3 font-mono">{c.args}</span>}
233
- </div>
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
- <div className="pt-2 space-y-2">
239
- <h3 className="text-xs font-semibold text-text-1">Tag Hierarchy</h3>
240
- <p className="text-xs text-text-3 leading-relaxed">
241
- Use <span className="font-mono text-accent">/</span> to create nested tags: <span className="font-mono text-accent">#groove/memory-system</span> lives under <span className="font-mono text-accent">#groove</span>. Pull a parent tag to get all children.
242
- </p>
243
- <h3 className="text-xs font-semibold text-text-1">Memory Types</h3>
244
- <p className="text-xs text-text-3 leading-relaxed">
245
- <span className="font-semibold">Manual</span> — you write it via [save], [append], [update]. <span className="font-semibold">Doc</span> AI writes it via [doc], synthesizing your conversation. Both are fully editable.
246
- </p>
247
- <h3 className="text-xs font-semibold text-text-1">Spawning with Context</h3>
248
- <p className="text-xs text-text-3 leading-relaxed">
249
- When spawning a new agent, use [pull] #tag to inject memories into their context. You can also link memories to NORTHSTAR docs with [link] for cross-referencing.
250
- </p>
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>