groove-dev 0.27.142 → 0.27.144

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 (187) hide show
  1. package/node_modules/@groove-dev/cli/package.json +1 -1
  2. package/node_modules/@groove-dev/daemon/package.json +1 -1
  3. package/node_modules/@groove-dev/daemon/src/api.js +1086 -6532
  4. package/node_modules/@groove-dev/daemon/src/gateways/manager.js +35 -1
  5. package/node_modules/@groove-dev/daemon/src/index.js +3 -0
  6. package/node_modules/@groove-dev/daemon/src/journalist.js +23 -13
  7. package/node_modules/@groove-dev/daemon/src/mlx-server.js +365 -0
  8. package/node_modules/@groove-dev/daemon/src/model-lab.js +308 -12
  9. package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
  10. package/node_modules/@groove-dev/daemon/src/process.js +2 -2
  11. package/node_modules/@groove-dev/daemon/src/providers/local.js +36 -8
  12. package/node_modules/@groove-dev/daemon/src/registry.js +21 -5
  13. package/node_modules/@groove-dev/daemon/src/routes/agents.js +889 -0
  14. package/node_modules/@groove-dev/daemon/src/routes/coordination.js +318 -0
  15. package/node_modules/@groove-dev/daemon/src/routes/files.js +751 -0
  16. package/node_modules/@groove-dev/daemon/src/routes/integrations.js +485 -0
  17. package/node_modules/@groove-dev/daemon/src/routes/network.js +1784 -0
  18. package/node_modules/@groove-dev/daemon/src/routes/providers.js +755 -0
  19. package/node_modules/@groove-dev/daemon/src/routes/schedules.js +110 -0
  20. package/node_modules/@groove-dev/daemon/src/routes/teams.js +650 -0
  21. package/node_modules/@groove-dev/daemon/src/scheduler.js +456 -24
  22. package/node_modules/@groove-dev/daemon/src/teams.js +1 -1
  23. package/node_modules/@groove-dev/daemon/src/validate.js +38 -1
  24. package/node_modules/@groove-dev/daemon/templates/mlx-setup.json +12 -0
  25. package/node_modules/@groove-dev/daemon/templates/tgi-setup.json +1 -1
  26. package/node_modules/@groove-dev/daemon/templates/vllm-setup.json +1 -1
  27. package/node_modules/@groove-dev/daemon/test/introducer.test.js +3 -3
  28. package/node_modules/@groove-dev/daemon/test/journalist.test.js +7 -10
  29. package/node_modules/@groove-dev/daemon/test/registry.test.js +38 -0
  30. package/node_modules/@groove-dev/gui/dist/assets/index-BcoF6_eF.js +1012 -0
  31. package/node_modules/@groove-dev/gui/dist/assets/index-Dd7qhiEd.css +1 -0
  32. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  33. package/node_modules/@groove-dev/gui/package.json +1 -1
  34. package/{packages/gui/src/app.jsx → node_modules/@groove-dev/gui/src/App.jsx} +0 -2
  35. package/node_modules/@groove-dev/gui/src/app.css +35 -0
  36. package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +1 -128
  37. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +144 -31
  38. package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +8 -13
  39. package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +159 -122
  40. package/node_modules/@groove-dev/gui/src/components/agents/diff-viewer.jsx +23 -23
  41. package/node_modules/@groove-dev/gui/src/components/agents/journalist-panel.jsx +1 -1
  42. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +2 -135
  43. package/node_modules/@groove-dev/gui/src/components/automations/automation-card.jsx +274 -0
  44. package/node_modules/@groove-dev/gui/src/components/automations/automation-wizard.jsx +1136 -0
  45. package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +3 -3
  46. package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +5 -5
  47. package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +6 -8
  48. package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  49. package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +238 -656
  50. package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +3 -3
  51. package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
  52. package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  53. package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +4 -4
  54. package/node_modules/@groove-dev/gui/src/components/editor/selection-menu.jsx +2 -0
  55. package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +316 -82
  56. package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +187 -32
  57. package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +195 -14
  58. package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +286 -102
  59. package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +2 -4
  60. package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +4 -2
  61. package/node_modules/@groove-dev/gui/src/components/layout/welcome-splash.jsx +137 -108
  62. package/node_modules/@groove-dev/gui/src/components/network/network-health.jsx +2 -2
  63. package/node_modules/@groove-dev/gui/src/components/network/performance-dashboard.jsx +4 -4
  64. package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +81 -99
  65. package/node_modules/@groove-dev/gui/src/components/ui/sheet.jsx +5 -2
  66. package/node_modules/@groove-dev/gui/src/lib/cron.js +64 -0
  67. package/node_modules/@groove-dev/gui/src/lib/status.js +24 -24
  68. package/node_modules/@groove-dev/gui/src/lib/theme-hex.js +1 -0
  69. package/node_modules/@groove-dev/gui/src/stores/groove.js +34 -3144
  70. package/node_modules/@groove-dev/gui/src/stores/helpers.js +10 -0
  71. package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +452 -0
  72. package/node_modules/@groove-dev/gui/src/stores/slices/automations-slice.js +96 -0
  73. package/node_modules/@groove-dev/gui/src/stores/slices/chat-slice.js +227 -0
  74. package/node_modules/@groove-dev/gui/src/stores/slices/editor-slice.js +285 -0
  75. package/node_modules/@groove-dev/gui/src/stores/slices/marketplace-slice.js +461 -0
  76. package/node_modules/@groove-dev/gui/src/stores/slices/network-slice.js +361 -0
  77. package/node_modules/@groove-dev/gui/src/stores/slices/preview-slice.js +109 -0
  78. package/node_modules/@groove-dev/gui/src/stores/slices/providers-slice.js +897 -0
  79. package/node_modules/@groove-dev/gui/src/stores/slices/teams-slice.js +413 -0
  80. package/node_modules/@groove-dev/gui/src/stores/slices/ui-slice.js +98 -0
  81. package/node_modules/@groove-dev/gui/src/views/agents.jsx +5 -5
  82. package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +12 -13
  83. package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +191 -3
  84. package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +17 -6
  85. package/node_modules/@groove-dev/gui/src/views/models.jsx +410 -509
  86. package/node_modules/@groove-dev/gui/src/views/network.jsx +3 -3
  87. package/node_modules/@groove-dev/gui/src/views/settings.jsx +81 -94
  88. package/node_modules/@groove-dev/gui/src/views/teams.jsx +40 -483
  89. package/package.json +1 -1
  90. package/packages/cli/package.json +1 -1
  91. package/packages/daemon/package.json +1 -1
  92. package/packages/daemon/src/api.js +1086 -6532
  93. package/packages/daemon/src/gateways/manager.js +35 -1
  94. package/packages/daemon/src/index.js +3 -0
  95. package/packages/daemon/src/journalist.js +23 -13
  96. package/packages/daemon/src/mlx-server.js +365 -0
  97. package/packages/daemon/src/model-lab.js +308 -12
  98. package/packages/daemon/src/pm.js +1 -1
  99. package/packages/daemon/src/process.js +2 -2
  100. package/packages/daemon/src/providers/local.js +36 -8
  101. package/packages/daemon/src/registry.js +21 -5
  102. package/packages/daemon/src/routes/agents.js +889 -0
  103. package/packages/daemon/src/routes/coordination.js +318 -0
  104. package/packages/daemon/src/routes/files.js +751 -0
  105. package/packages/daemon/src/routes/integrations.js +485 -0
  106. package/packages/daemon/src/routes/network.js +1784 -0
  107. package/packages/daemon/src/routes/providers.js +755 -0
  108. package/packages/daemon/src/routes/schedules.js +110 -0
  109. package/packages/daemon/src/routes/teams.js +650 -0
  110. package/packages/daemon/src/scheduler.js +456 -24
  111. package/packages/daemon/src/teams.js +1 -1
  112. package/packages/daemon/src/validate.js +38 -1
  113. package/packages/daemon/templates/mlx-setup.json +12 -0
  114. package/packages/daemon/templates/tgi-setup.json +1 -1
  115. package/packages/daemon/templates/vllm-setup.json +1 -1
  116. package/packages/gui/dist/assets/index-BcoF6_eF.js +1012 -0
  117. package/packages/gui/dist/assets/index-Dd7qhiEd.css +1 -0
  118. package/packages/gui/dist/index.html +2 -2
  119. package/packages/gui/package.json +1 -1
  120. package/{node_modules/@groove-dev/gui/src/app.jsx → packages/gui/src/App.jsx} +0 -2
  121. package/packages/gui/src/app.css +35 -0
  122. package/packages/gui/src/components/agents/agent-config.jsx +1 -128
  123. package/packages/gui/src/components/agents/agent-feed.jsx +144 -31
  124. package/packages/gui/src/components/agents/agent-node.jsx +8 -13
  125. package/packages/gui/src/components/agents/code-review.jsx +159 -122
  126. package/packages/gui/src/components/agents/diff-viewer.jsx +23 -23
  127. package/packages/gui/src/components/agents/journalist-panel.jsx +1 -1
  128. package/packages/gui/src/components/agents/spawn-wizard.jsx +2 -135
  129. package/packages/gui/src/components/automations/automation-card.jsx +274 -0
  130. package/packages/gui/src/components/automations/automation-wizard.jsx +1136 -0
  131. package/packages/gui/src/components/dashboard/activity-feed.jsx +3 -3
  132. package/packages/gui/src/components/dashboard/cache-ring.jsx +5 -5
  133. package/packages/gui/src/components/dashboard/context-gauges.jsx +6 -8
  134. package/packages/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  135. package/packages/gui/src/components/dashboard/intel-panel.jsx +238 -656
  136. package/packages/gui/src/components/dashboard/kpi-card.jsx +3 -3
  137. package/packages/gui/src/components/dashboard/routing-chart.jsx +3 -3
  138. package/packages/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  139. package/packages/gui/src/components/dashboard/token-chart.jsx +4 -4
  140. package/packages/gui/src/components/editor/selection-menu.jsx +2 -0
  141. package/packages/gui/src/components/lab/lab-assistant.jsx +316 -82
  142. package/packages/gui/src/components/lab/metrics-panel.jsx +187 -32
  143. package/packages/gui/src/components/lab/parameter-panel.jsx +195 -14
  144. package/packages/gui/src/components/lab/runtime-config.jsx +286 -102
  145. package/packages/gui/src/components/layout/activity-bar.jsx +2 -4
  146. package/packages/gui/src/components/layout/terminal-panel.jsx +4 -2
  147. package/packages/gui/src/components/layout/welcome-splash.jsx +137 -108
  148. package/packages/gui/src/components/network/network-health.jsx +2 -2
  149. package/packages/gui/src/components/network/performance-dashboard.jsx +4 -4
  150. package/packages/gui/src/components/settings/ssh-wizard.jsx +81 -99
  151. package/packages/gui/src/components/ui/sheet.jsx +5 -2
  152. package/packages/gui/src/lib/cron.js +64 -0
  153. package/packages/gui/src/lib/status.js +24 -24
  154. package/packages/gui/src/lib/theme-hex.js +1 -0
  155. package/packages/gui/src/stores/groove.js +34 -3144
  156. package/packages/gui/src/stores/helpers.js +10 -0
  157. package/packages/gui/src/stores/slices/agents-slice.js +452 -0
  158. package/packages/gui/src/stores/slices/automations-slice.js +96 -0
  159. package/packages/gui/src/stores/slices/chat-slice.js +227 -0
  160. package/packages/gui/src/stores/slices/editor-slice.js +285 -0
  161. package/packages/gui/src/stores/slices/marketplace-slice.js +461 -0
  162. package/packages/gui/src/stores/slices/network-slice.js +361 -0
  163. package/packages/gui/src/stores/slices/preview-slice.js +109 -0
  164. package/packages/gui/src/stores/slices/providers-slice.js +897 -0
  165. package/packages/gui/src/stores/slices/teams-slice.js +413 -0
  166. package/packages/gui/src/stores/slices/ui-slice.js +98 -0
  167. package/packages/gui/src/views/agents.jsx +5 -5
  168. package/packages/gui/src/views/dashboard.jsx +12 -13
  169. package/packages/gui/src/views/marketplace.jsx +191 -3
  170. package/packages/gui/src/views/model-lab.jsx +17 -6
  171. package/packages/gui/src/views/models.jsx +410 -509
  172. package/packages/gui/src/views/network.jsx +3 -3
  173. package/packages/gui/src/views/settings.jsx +81 -94
  174. package/packages/gui/src/views/teams.jsx +40 -483
  175. package/SECURITY_SWEEP.md +0 -228
  176. package/TRAINING_DATA_v4.md +0 -6
  177. package/node_modules/@groove-dev/gui/dist/assets/index-Bjd91ufV.js +0 -984
  178. package/node_modules/@groove-dev/gui/dist/assets/index-BqdwIFn4.css +0 -1
  179. package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +0 -322
  180. package/node_modules/@groove-dev/gui/src/views/preview.jsx +0 -6
  181. package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +0 -327
  182. package/packages/gui/dist/assets/index-Bjd91ufV.js +0 -984
  183. package/packages/gui/dist/assets/index-BqdwIFn4.css +0 -1
  184. package/packages/gui/src/components/agents/agent-chat.jsx +0 -322
  185. package/packages/gui/src/views/preview.jsx +0 -6
  186. package/packages/gui/src/views/subscription-panel.jsx +0 -327
  187. package/test.py +0 -571
@@ -5,6 +5,7 @@ import {
5
5
  FileEdit, Search, Terminal, CheckCircle2, AlertCircle,
6
6
  RotateCw, Zap, Wrench, Eye, Code2, Bug,
7
7
  ChevronDown, HelpCircle, Pencil, Paperclip, GripHorizontal,
8
+ FileCode, X,
8
9
  } from 'lucide-react';
9
10
  import { AnimatePresence, motion } from 'framer-motion';
10
11
  import { useGrooveStore } from '../../stores/groove';
@@ -15,6 +16,18 @@ import { ThinkingIndicator } from '../ui/thinking-indicator';
15
16
  import { TableTree } from '../ui/table-tree';
16
17
 
17
18
  const EMPTY = [];
19
+ const KEEPER_RE = /(\[(?:save|append|update|delete|view|doc|link|read|instruct)\]|#[\w/.-]+)/gi;
20
+ const KEEPER_CMD_RE = /^\[(?:save|append|update|delete|view|doc|link|read|instruct)\]$/i;
21
+ const KEEPER_TAG_RE = /^#[\w/.-]+$/;
22
+ const KEEPER_DETECT_RE = /\[(?:save|append|update|delete|view|doc|link|read|instruct)\]|#[\w/.-]+/i;
23
+
24
+ function highlightKeeperInput(text) {
25
+ return text.split(KEEPER_RE).map((part, i) => {
26
+ if (KEEPER_CMD_RE.test(part)) return <span key={i} className="text-accent font-semibold">{part}</span>;
27
+ if (KEEPER_TAG_RE.test(part)) return <span key={i} className="text-accent">{part}</span>;
28
+ return <span key={i} className="text-text-0">{part}</span>;
29
+ });
30
+ }
18
31
 
19
32
  // ── Activity metadata ────────────────────────────────────────
20
33
  function activityMeta(text) {
@@ -369,7 +382,7 @@ function StreamingBar({ agent }) {
369
382
  <div className="flex items-center gap-3 px-4 h-8 border-b border-border-subtle bg-surface-1/80 flex-shrink-0">
370
383
  <div className="flex items-center gap-2 flex-1 min-w-0">
371
384
  <div className="relative flex items-center justify-center w-4 h-4">
372
- <span className="absolute inset-0 rounded-full bg-accent/15 animate-ping" style={{ animationDuration: '2s' }} />
385
+ <span className="absolute inset-0 rounded-full bg-accent/15 animate-ping [animation-duration:2s]" />
373
386
  <span className="relative w-1.5 h-1.5 rounded-full bg-accent" />
374
387
  </div>
375
388
  {isRecent ? (
@@ -432,8 +445,8 @@ function BootSequence({ agent }) {
432
445
  {/* Agent identity */}
433
446
  <div className="flex items-center gap-3 mb-5">
434
447
  <div className="relative w-9 h-9">
435
- <span className="absolute inset-0 rounded-full border-2 border-accent/20 animate-ping" style={{ animationDuration: '2s' }} />
436
- <span className="absolute inset-0 rounded-full border-2 border-transparent border-t-accent animate-spin" style={{ animationDuration: '1s' }} />
448
+ <span className="absolute inset-0 rounded-full border-2 border-accent/20 animate-ping [animation-duration:2s]" />
449
+ <span className="absolute inset-0 rounded-full border-2 border-transparent border-t-accent animate-spin [animation-duration:1s]" />
437
450
  <span className="absolute inset-[5px] rounded-full bg-accent/8" />
438
451
  </div>
439
452
  <div>
@@ -467,9 +480,9 @@ function BootSequence({ agent }) {
467
480
  </span>
468
481
  {isLast && visible && (
469
482
  <span className="flex gap-0.5 ml-1">
470
- <span className="w-1 h-1 rounded-full bg-accent animate-pulse" style={{ animationDelay: '0ms' }} />
471
- <span className="w-1 h-1 rounded-full bg-accent animate-pulse" style={{ animationDelay: '200ms' }} />
472
- <span className="w-1 h-1 rounded-full bg-accent animate-pulse" style={{ animationDelay: '400ms' }} />
483
+ <span className="w-1 h-1 rounded-full bg-accent animate-pulse [animation-delay:0ms]" />
484
+ <span className="w-1 h-1 rounded-full bg-accent animate-pulse [animation-delay:200ms]" />
485
+ <span className="w-1 h-1 rounded-full bg-accent animate-pulse [animation-delay:400ms]" />
473
486
  </span>
474
487
  )}
475
488
  </div>
@@ -480,6 +493,33 @@ function BootSequence({ agent }) {
480
493
  );
481
494
  }
482
495
 
496
+ // ── Snippet Tag ─────────────────────────────────────────────
497
+
498
+ function SnippetTag({ snippet, onRemove }) {
499
+ const isCode = snippet.type === 'code';
500
+ const Icon = isCode ? FileCode : Terminal;
501
+ const lines = snippet.code.split('\n').length;
502
+ let label;
503
+ if (isCode && snippet.filePath) {
504
+ const fileName = snippet.filePath.split('/').pop();
505
+ label = `${fileName}:${snippet.lineStart}-${snippet.lineEnd}`;
506
+ } else {
507
+ label = `${isCode ? '' : 'Terminal · '}${lines} line${lines !== 1 ? 's' : ''}`;
508
+ }
509
+ return (
510
+ <div className="flex items-center gap-1.5 px-2 py-1 rounded-md bg-accent/10 border border-accent/20 text-accent">
511
+ <Icon size={11} className="flex-shrink-0" />
512
+ <span className="text-2xs font-sans font-medium truncate max-w-[160px]">{label}</span>
513
+ {snippet.instruction && (
514
+ <span className="text-2xs text-accent/60 truncate max-w-[100px]">· {snippet.instruction}</span>
515
+ )}
516
+ <button onClick={onRemove} className="p-0.5 rounded hover:bg-accent/20 cursor-pointer flex-shrink-0">
517
+ <X size={9} />
518
+ </button>
519
+ </div>
520
+ );
521
+ }
522
+
483
523
  // ── Main Feed ────────────────────────────────────────────────
484
524
 
485
525
  export function AgentFeed({ agent }) {
@@ -495,8 +535,15 @@ export function AgentFeed({ agent }) {
495
535
  const chatHistory = rawChatHistory.length > 0 ? rawChatHistory : cachedChatRef.current;
496
536
  const activityLog = rawActivityLog.length > 0 ? rawActivityLog : cachedActivityRef.current;
497
537
 
538
+ const pendingSnippet = useGrooveStore((s) => s.editorPendingSnippet);
539
+ const clearSnippet = useGrooveStore((s) => s.clearSnippet);
540
+
498
541
  const storeInput = useGrooveStore((s) => s.chatInputs[agent.id] || '');
499
- const setStoreInput = (val) => useGrooveStore.setState((s) => ({ chatInputs: { ...s.chatInputs, [agent.id]: val } }));
542
+ const setStoreInput = (val) => useGrooveStore.setState((s) => {
543
+ const current = s.chatInputs[agent.id] || '';
544
+ const next = typeof val === 'function' ? val(current) : val;
545
+ return { chatInputs: { ...s.chatInputs, [agent.id]: next } };
546
+ });
500
547
  const input = storeInput;
501
548
  const setInput = setStoreInput;
502
549
  const [mode, setMode] = useState('instruct'); // instruct | query
@@ -506,8 +553,13 @@ export function AgentFeed({ agent }) {
506
553
  const scrollRef = useRef(null);
507
554
  const inputRef = useRef(null);
508
555
  const fileInputRef = useRef(null);
556
+ const highlightRef = useRef(null);
509
557
  const isAtBottomRef = useRef(true);
510
558
 
559
+ useEffect(() => {
560
+ if (pendingSnippet) inputRef.current?.focus();
561
+ }, [pendingSnippet]);
562
+
511
563
  useEffect(() => {
512
564
  const el = scrollRef.current;
513
565
  if (!el) return;
@@ -616,7 +668,7 @@ export function AgentFeed({ agent }) {
616
668
 
617
669
  async function handleSend() {
618
670
  const text = input.trim();
619
- if (!text || sending) return;
671
+ if ((!text && !pendingSnippet) || sending) return;
620
672
 
621
673
  if (text === '/rotate') {
622
674
  const rotateAgent = useGrooveStore.getState().rotateAgent;
@@ -625,7 +677,22 @@ export function AgentFeed({ agent }) {
625
677
  return;
626
678
  }
627
679
 
680
+ const parts = [];
681
+ if (text) parts.push(text);
682
+ if (pendingSnippet) {
683
+ const s = pendingSnippet;
684
+ if (s.type === 'code' && s.filePath) {
685
+ if (s.instruction && !text) parts.push(s.instruction);
686
+ parts.push(`File: ${s.filePath} (lines ${s.lineStart}-${s.lineEnd})`);
687
+ parts.push('```\n' + s.code + '\n```');
688
+ } else if (s.code) {
689
+ parts.push('```\n' + s.code + '\n```');
690
+ }
691
+ }
692
+ const message = parts.join('\n\n');
693
+
628
694
  setInput('');
695
+ clearSnippet();
629
696
  setSending(true);
630
697
  isAtBottomRef.current = true;
631
698
  requestAnimationFrame(() => {
@@ -633,9 +700,9 @@ export function AgentFeed({ agent }) {
633
700
  });
634
701
  try {
635
702
  if (mode === 'query') {
636
- await queryAgent(agent.id, text);
703
+ await queryAgent(agent.id, message);
637
704
  } else {
638
- await instructAgent(agent.id, text);
705
+ await instructAgent(agent.id, message);
639
706
  }
640
707
  } catch { /* toast handles */ }
641
708
  setSending(false);
@@ -707,6 +774,26 @@ export function AgentFeed({ agent }) {
707
774
  </div>
708
775
 
709
776
  <div className="px-4 pb-3">
777
+ {/* Snippet tag */}
778
+ {pendingSnippet && (
779
+ <div className="mb-2">
780
+ <SnippetTag snippet={pendingSnippet} onRemove={clearSnippet} />
781
+ </div>
782
+ )}
783
+
784
+ {/* Keeper command indicator */}
785
+ {input && /\[(?:save|append|update|delete|view|doc|link|read|instruct)\]/i.test(input) && (() => {
786
+ const cmdMatch = input.match(/\[(save|append|update|delete|view|doc|link|read|instruct)\]/i);
787
+ const tags = (input.match(/#[\w/.-]+/g) || []);
788
+ return (
789
+ <div className="flex items-center gap-1.5 px-3 py-1 mb-2 rounded-lg bg-accent/5 border border-accent/10">
790
+ <span className="px-1.5 py-0.5 rounded bg-accent/15 text-accent font-semibold font-mono text-[10px]">{cmdMatch[0]}</span>
791
+ {tags.map((tag, i) => <span key={i} className="text-accent font-medium text-[10px]">{tag}</span>)}
792
+ <span className="text-[10px] text-text-4 ml-auto">memory command</span>
793
+ </div>
794
+ );
795
+ })()}
796
+
710
797
  {/* Mode pills */}
711
798
  <div className="flex items-center gap-1 mb-2">
712
799
  <button
@@ -757,42 +844,68 @@ export function AgentFeed({ agent }) {
757
844
  >
758
845
  <Paperclip size={14} />
759
846
  </button>
760
- <textarea
761
- ref={inputRef}
762
- value={input}
763
- onChange={(e) => setInput(e.target.value)}
764
- onKeyDown={onKeyDown}
765
- placeholder={mode === 'query'
766
- ? 'Ask about this agent\'s work...'
767
- : isAlive ? 'Send an instruction...' : 'Continue this session...'}
768
- rows={1}
769
- className={cn(
770
- 'flex-1 resize-none px-3 py-2 text-[13px]',
771
- 'bg-transparent text-text-0 font-sans',
772
- 'placeholder:text-text-4',
773
- 'focus:outline-none',
847
+ <div className="flex-1 relative">
848
+ {input && KEEPER_DETECT_RE.test(input) && (
849
+ <div
850
+ ref={highlightRef}
851
+ aria-hidden
852
+ className="absolute inset-0 px-3 py-2 text-[13px] font-sans pointer-events-none whitespace-pre-wrap break-words overflow-y-hidden"
853
+ style={{ height: inputHeight }}
854
+ >
855
+ {highlightKeeperInput(input)}
856
+ </div>
774
857
  )}
775
- style={{ height: inputHeight }}
776
- />
858
+ <textarea
859
+ ref={inputRef}
860
+ value={input}
861
+ onChange={(e) => setInput(e.target.value)}
862
+ onKeyDown={onKeyDown}
863
+ onScroll={(e) => { if (highlightRef.current) highlightRef.current.scrollTop = e.target.scrollTop; }}
864
+ onDragOver={(e) => e.preventDefault()}
865
+ onDrop={(e) => {
866
+ e.preventDefault();
867
+ if (e.dataTransfer?.files?.length) {
868
+ const dt = new DataTransfer();
869
+ for (const f of e.dataTransfer.files) dt.items.add(f);
870
+ fileInputRef.current.files = dt.files;
871
+ fileInputRef.current.dispatchEvent(new Event('change', { bubbles: true }));
872
+ }
873
+ }}
874
+ placeholder={pendingSnippet ? 'Add a message (optional)...'
875
+ : mode === 'query' ? 'Ask about this agent\'s work...'
876
+ : isAlive ? 'Send an instruction...' : 'Continue this session...'}
877
+ rows={1}
878
+ className={cn(
879
+ 'w-full resize-none px-3 py-2 text-[13px]',
880
+ 'bg-transparent font-sans relative z-10',
881
+ 'placeholder:text-text-4',
882
+ 'focus:outline-none',
883
+ input && KEEPER_DETECT_RE.test(input)
884
+ ? 'text-transparent caret-text-0'
885
+ : 'text-text-0',
886
+ )}
887
+ style={{ height: inputHeight }}
888
+ />
889
+ </div>
777
890
  {isAlive && (
778
891
  <button
779
892
  onClick={() => useGrooveStore.getState().stopAgent(agent.id)}
780
893
  title="Stop agent"
781
- className="w-9 h-9 flex items-center justify-center rounded-lg hover:bg-danger/10 transition-colors cursor-pointer flex-shrink-0 mb-px"
894
+ className="group w-9 h-9 flex items-center justify-center rounded-lg hover:bg-red-500/10 transition-colors cursor-pointer flex-shrink-0 mb-px"
782
895
  >
783
896
  <span className="relative flex items-center justify-center w-3.5 h-3.5">
784
- <span className="absolute inset-0 rounded-full bg-danger/30 animate-ping" style={{ animationDuration: '2s' }} />
785
- <span className="relative w-2.5 h-2.5 rounded-full bg-danger" />
897
+ <span className="absolute inset-0 rounded-full bg-accent/30 group-hover:bg-red-500/30 animate-ping [animation-duration:2s] transition-colors" />
898
+ <span className="relative w-2.5 h-2.5 rounded-full bg-accent group-hover:bg-red-500 transition-colors" />
786
899
  </span>
787
900
  </button>
788
901
  )}
789
902
  <button
790
903
  onClick={handleSend}
791
- disabled={!input.trim() || sending}
904
+ disabled={(!input.trim() && !pendingSnippet) || sending}
792
905
  className={cn(
793
906
  'w-9 h-9 flex items-center justify-center rounded-lg transition-all cursor-pointer flex-shrink-0 mb-px',
794
907
  'disabled:opacity-15 disabled:cursor-not-allowed',
795
- input.trim()
908
+ (input.trim() || pendingSnippet)
796
909
  ? mode === 'query'
797
910
  ? 'bg-info/15 text-info hover:bg-info/25 border border-info/25'
798
911
  : 'bg-accent/15 text-accent hover:bg-accent/25 border border-accent/25'
@@ -8,7 +8,6 @@ import { fmtNum, fmtDollar, fmtUptime } from '../../lib/format';
8
8
 
9
9
  const EMPTY = [];
10
10
  const ERROR_RE = /error|crash|fail/i;
11
- const BAR_BG = 'rgba(51, 175, 188, 0.15)';
12
11
  const BAR_H = 'h-[2px]';
13
12
 
14
13
  function shortModel(id) {
@@ -98,13 +97,9 @@ const AgentNode = memo(({ data, selected }) => {
98
97
 
99
98
  {/* Scan line — running only */}
100
99
  {isAlive && (
101
- <div className="absolute inset-0 overflow-hidden pointer-events-none" style={{ borderRadius: 3 }}>
100
+ <div className="absolute inset-0 overflow-hidden pointer-events-none rounded-[3px]">
102
101
  <div
103
- className="absolute left-0 right-0 h-px"
104
- style={{
105
- background: 'linear-gradient(90deg, transparent 0%, rgba(97,175,239,0.25) 50%, transparent 100%)',
106
- animation: 'node-scan 3s ease-in-out infinite',
107
- }}
102
+ className="absolute left-0 right-0 h-px [background:linear-gradient(90deg,transparent_0%,rgba(97,175,239,0.25)_50%,transparent_100%)] [animation:node-scan_3s_ease-in-out_infinite]"
108
103
  />
109
104
  </div>
110
105
  )}
@@ -116,8 +111,8 @@ const AgentNode = memo(({ data, selected }) => {
116
111
  <span className="absolute inset-0 rounded-sm" style={{ background: sColor }} />
117
112
  {isAlive && (
118
113
  <span
119
- className="absolute inset-[-2px] rounded-sm"
120
- style={{ background: sColor, opacity: 0.15, animation: 'node-pulse-bar 2s ease-in-out infinite' }}
114
+ className="absolute inset-[-2px] rounded-sm opacity-[0.15] [animation:node-pulse-bar_2s_ease-in-out_infinite]"
115
+ style={{ background: sColor }}
121
116
  />
122
117
  )}
123
118
  </span>
@@ -155,7 +150,7 @@ const AgentNode = memo(({ data, selected }) => {
155
150
  </div>
156
151
 
157
152
  {/* Context bar */}
158
- <div className={`mt-1.5 ${BAR_H} rounded-sm overflow-hidden`} style={{ background: BAR_BG }}>
153
+ <div className={`mt-1.5 ${BAR_H} rounded-sm overflow-hidden bg-[rgba(51,175,188,0.15)]`}>
159
154
  <div
160
155
  className="h-full rounded-sm transition-all duration-700"
161
156
  style={{
@@ -185,7 +180,7 @@ const AgentNode = memo(({ data, selected }) => {
185
180
  )}
186
181
  </div>
187
182
  <div className="flex items-center gap-2">
188
- <div className={`flex-1 ${BAR_H} rounded-sm overflow-hidden`} style={{ background: BAR_BG }}>
183
+ <div className={`flex-1 ${BAR_H} rounded-sm overflow-hidden bg-[rgba(51,175,188,0.15)]`}>
189
184
  <div className="h-full rounded-sm transition-all duration-500" style={{ width: `${Math.max(contextPct, 1)}%`, background: ctxColor }} />
190
185
  </div>
191
186
  <span className="text-[9px] font-mono font-medium" style={{ color: ctxColor }}>{contextPct}%</span>
@@ -196,7 +191,7 @@ const AgentNode = memo(({ data, selected }) => {
196
191
  <div className="px-3 pt-1 pb-1">
197
192
  <span className="text-[9px] font-mono text-[#505862] uppercase tracking-wider">Quality</span>
198
193
  <div className="flex items-center gap-2 mt-1">
199
- <div className={`flex-1 ${BAR_H} rounded-sm overflow-hidden`} style={{ background: BAR_BG }}>
194
+ <div className={`flex-1 ${BAR_H} rounded-sm overflow-hidden bg-[rgba(51,175,188,0.15)]`}>
200
195
  <div className="h-full rounded-sm transition-all duration-500" style={{ width: `${qScore != null ? Math.max(qScore, 1) : 0}%`, background: qColor || '#505862' }} />
201
196
  </div>
202
197
  <span className="text-[9px] font-mono font-medium" style={{ color: qColor || '#505862' }}>{qScore != null ? qScore : '—'}</span>
@@ -207,7 +202,7 @@ const AgentNode = memo(({ data, selected }) => {
207
202
  <div className="px-3 pt-1 pb-1">
208
203
  <span className="text-[9px] font-mono text-[#505862] uppercase tracking-wider">Efficiency</span>
209
204
  <div className="flex items-center gap-2 mt-1">
210
- <div className={`flex-1 ${BAR_H} rounded-sm overflow-hidden`} style={{ background: BAR_BG }}>
205
+ <div className={`flex-1 ${BAR_H} rounded-sm overflow-hidden bg-[rgba(51,175,188,0.15)]`}>
211
206
  <div className="h-full rounded-sm transition-all duration-500" style={{ width: `${effPct != null ? Math.max(effPct, 1) : 0}%`, background: effColor || '#505862' }} />
212
207
  </div>
213
208
  <span className="text-[9px] font-mono font-medium" style={{ color: effColor || '#505862' }}>{effPct != null ? `${effPct}%` : '—'}</span>