groove-dev 0.27.142 → 0.27.143

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.
@@ -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-Bjd91ufV.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-DGIv_TRm.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-BqdwIFn4.css">
14
+ <link rel="stylesheet" crossorigin href="/assets/index-CCVvAoQn.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.142",
3
+ "version": "0.27.143",
4
4
  "description": "GROOVE GUI — visual agent control plane",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  // FSL-1.1-Apache-2.0 — see LICENSE
2
2
  import { useState, useRef, useEffect } from 'react';
3
- import { Send, Loader2, MessageSquare, HelpCircle, ArrowRight, Paperclip, Square } from 'lucide-react';
3
+ import { Send, Loader2, MessageSquare, HelpCircle, ArrowRight, Paperclip, Square, FileCode, Terminal as TerminalIcon, X } from 'lucide-react';
4
4
  import { useGrooveStore } from '../../stores/groove';
5
5
  import { cn } from '../../lib/cn';
6
6
  import { ThinkingIndicator } from '../ui/thinking-indicator';
@@ -145,12 +145,40 @@ function TypingIndicator() {
145
145
  );
146
146
  }
147
147
 
148
+ function SnippetTag({ snippet, onRemove }) {
149
+ const isCode = snippet.type === 'code';
150
+ const Icon = isCode ? FileCode : TerminalIcon;
151
+ const lines = snippet.code.split('\n').length;
152
+ let label;
153
+ if (isCode && snippet.filePath) {
154
+ const fileName = snippet.filePath.split('/').pop();
155
+ label = `${fileName}:${snippet.lineStart}-${snippet.lineEnd}`;
156
+ } else {
157
+ label = `${isCode ? '' : 'Terminal · '}${lines} line${lines !== 1 ? 's' : ''}`;
158
+ }
159
+ return (
160
+ <div className="flex items-center gap-1.5 px-2 py-1 rounded-md bg-accent/10 border border-accent/20 text-accent">
161
+ <Icon size={11} className="flex-shrink-0" />
162
+ <span className="text-2xs font-sans font-medium truncate max-w-[160px]">{label}</span>
163
+ {snippet.instruction && (
164
+ <span className="text-2xs text-accent/60 truncate max-w-[100px]">· {snippet.instruction}</span>
165
+ )}
166
+ <button onClick={onRemove} className="p-0.5 rounded hover:bg-accent/20 cursor-pointer flex-shrink-0">
167
+ <X size={9} />
168
+ </button>
169
+ </div>
170
+ );
171
+ }
172
+
148
173
  export function AgentChat({ agent }) {
149
174
  const chatHistory = useGrooveStore((s) => s.chatHistory[agent.id]) || EMPTY;
150
175
  const activityLog = useGrooveStore((s) => s.activityLog[agent.id]) || EMPTY;
151
176
  const instructAgent = useGrooveStore((s) => s.instructAgent);
152
177
  const isThinking = useGrooveStore((s) => s.thinkingAgents?.has(agent.id));
153
178
 
179
+ const pendingSnippet = useGrooveStore((s) => s.editorPendingSnippet);
180
+ const clearSnippet = useGrooveStore((s) => s.clearSnippet);
181
+
154
182
  const storeInput = useGrooveStore((s) => s.chatInputs[agent.id] || '');
155
183
  const setStoreInput = (val) => useGrooveStore.setState((s) => ({ chatInputs: { ...s.chatInputs, [agent.id]: val } }));
156
184
  const input = storeInput;
@@ -162,6 +190,10 @@ export function AgentChat({ agent }) {
162
190
  const fileInputRef = useRef(null);
163
191
  const isAtBottomRef = useRef(true);
164
192
 
193
+ useEffect(() => {
194
+ if (pendingSnippet) inputRef.current?.focus();
195
+ }, [pendingSnippet]);
196
+
165
197
  useEffect(() => {
166
198
  const el = scrollRef.current;
167
199
  if (!el) return;
@@ -192,16 +224,30 @@ export function AgentChat({ agent }) {
192
224
 
193
225
  async function handleSend() {
194
226
  const text = input.trim();
195
- if (!text || sending) return;
227
+ if ((!text && !pendingSnippet) || sending) return;
228
+ const parts = [];
229
+ if (text) parts.push(text);
230
+ if (pendingSnippet) {
231
+ const s = pendingSnippet;
232
+ if (s.type === 'code' && s.filePath) {
233
+ if (s.instruction && !text) parts.push(s.instruction);
234
+ parts.push(`File: ${s.filePath} (lines ${s.lineStart}-${s.lineEnd})`);
235
+ parts.push('```\n' + s.code + '\n```');
236
+ } else if (s.code) {
237
+ parts.push('```\n' + s.code + '\n```');
238
+ }
239
+ }
240
+ const message = parts.join('\n\n');
196
241
  setInput('');
197
242
  setAttachedFiles([]);
243
+ clearSnippet();
198
244
  setSending(true);
199
245
  isAtBottomRef.current = true;
200
246
  requestAnimationFrame(() => {
201
247
  if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
202
248
  });
203
249
  try {
204
- await instructAgent(agent.id, text);
250
+ await instructAgent(agent.id, message);
205
251
  } catch { /* toast handles */ }
206
252
  setSending(false);
207
253
  inputRef.current?.focus();
@@ -251,6 +297,22 @@ export function AgentChat({ agent }) {
251
297
 
252
298
  {/* ── Input area ──────────────────────────────────── */}
253
299
  <div className="border-t border-border-subtle px-3 py-2 bg-surface-1">
300
+ {pendingSnippet && (
301
+ <div className="mb-1.5">
302
+ <SnippetTag snippet={pendingSnippet} onRemove={clearSnippet} />
303
+ </div>
304
+ )}
305
+ {input && /\[(?:save|append|update|delete|view|doc|link|read|instruct)\]/i.test(input) && (() => {
306
+ const cmdMatch = input.match(/\[(save|append|update|delete|view|doc|link|read|instruct)\]/i);
307
+ const tags = (input.match(/#[\w/.-]+/g) || []);
308
+ return (
309
+ <div className="flex items-center gap-1.5 px-3 py-1 mb-1.5 rounded-md bg-accent/5 border border-accent/10">
310
+ <span className="px-1.5 py-0.5 rounded bg-accent/15 text-accent font-semibold font-mono text-2xs">{cmdMatch[0]}</span>
311
+ {tags.map((tag, i) => <span key={i} className="text-accent font-medium text-2xs">{tag}</span>)}
312
+ <span className="text-2xs text-text-4 ml-auto">memory command</span>
313
+ </div>
314
+ );
315
+ })()}
254
316
  <div className="flex items-end gap-1.5">
255
317
  <input
256
318
  ref={fileInputRef}
@@ -267,28 +329,23 @@ export function AgentChat({ agent }) {
267
329
  >
268
330
  <Paperclip size={14} />
269
331
  </button>
270
- <div className="flex-1 relative bg-surface-0 rounded-lg border border-border focus-within:ring-1 focus-within:ring-accent/40">
271
- <div
272
- aria-hidden
273
- className="absolute inset-0 px-3 py-1.5 text-xs font-sans pointer-events-none whitespace-pre-wrap break-words overflow-hidden min-h-[32px] max-h-[120px] leading-[1.625]"
274
- >
275
- {input ? highlightKeeper(input) : null}
276
- </div>
332
+ <div className="flex-1">
277
333
  <textarea
278
334
  ref={inputRef}
279
335
  value={input}
280
336
  onChange={(e) => setInput(e.target.value)}
281
337
  onKeyDown={onKeyDown}
282
- placeholder={isAlive ? 'Instruct this agent...' : 'Continue conversation...'}
338
+ placeholder={pendingSnippet ? 'Add a message (optional)...' : isAlive ? 'Instruct this agent...' : 'Continue conversation...'}
283
339
  rows={1}
284
340
  className={cn(
285
341
  'w-full resize-none rounded-lg px-3 py-1.5 text-xs',
286
- 'bg-transparent font-sans relative z-10',
342
+ 'bg-surface-0 border font-sans',
287
343
  'placeholder:text-text-4',
288
- 'focus:outline-none',
344
+ 'focus:outline-none focus:ring-1',
289
345
  'min-h-[32px] max-h-[120px]',
290
- input && /(\[(?:save|append|update|delete|view|doc|link|read|instruct)\]|#[\w/.-]+)/i.test(input)
291
- ? 'text-transparent caret-text-0'
346
+ 'border-border focus:ring-accent/40',
347
+ input && /\[(?:save|append|update|delete|view|doc|link|read|instruct)\]/i.test(input)
348
+ ? 'text-accent'
292
349
  : 'text-text-0',
293
350
  )}
294
351
  />
@@ -304,11 +361,11 @@ export function AgentChat({ agent }) {
304
361
  )}
305
362
  <button
306
363
  onClick={handleSend}
307
- disabled={!input.trim() || sending}
364
+ disabled={(!input.trim() && !pendingSnippet) || sending}
308
365
  className={cn(
309
366
  'w-8 h-8 flex items-center justify-center rounded-lg transition-all cursor-pointer flex-shrink-0',
310
367
  'disabled:opacity-20 disabled:cursor-not-allowed',
311
- input.trim()
368
+ (input.trim() || pendingSnippet)
312
369
  ? 'bg-accent/15 text-accent hover:bg-accent/25 border border-accent/25'
313
370
  : 'bg-surface-4 text-text-4',
314
371
  )}
@@ -18,6 +18,7 @@ export function SelectionMenu({ x, y, filePath, lineStart, lineEnd, selectedCode
18
18
  const sendCodeToAgent = useGrooveStore((s) => s.sendCodeToAgent);
19
19
  const toggleAiPanel = useGrooveStore((s) => s.toggleAiPanel);
20
20
  const aiPanelOpen = useGrooveStore((s) => s.editorAiPanelOpen);
21
+ const selectAgent = useGrooveStore((s) => s.selectAgent);
21
22
 
22
23
  useEffect(() => {
23
24
  function handleClick(e) {
@@ -51,6 +52,7 @@ export function SelectionMenu({ x, y, filePath, lineStart, lineEnd, selectedCode
51
52
  if (!agentId) return;
52
53
  sendCodeToAgent(agentId, action.instruction, filePath, lineStart, lineEnd, selectedCode);
53
54
  if (!aiPanelOpen) toggleAiPanel();
55
+ selectAgent(agentId);
54
56
  onClose();
55
57
  }
56
58
 
@@ -1,6 +1,5 @@
1
1
  // FSL-1.1-Apache-2.0 — see LICENSE
2
2
  import { useState, useEffect, useRef, useMemo } from 'react';
3
- import { ScrollArea } from '../components/ui/scroll-area';
4
3
  import { Badge } from '../components/ui/badge';
5
4
  import { Button } from '../components/ui/button';
6
5
  import { api } from '../lib/api';
@@ -749,7 +748,7 @@ export default function ModelsView() {
749
748
  </div>
750
749
 
751
750
  {/* ════ ZONE 2 + 3: Scrollable Content ════ */}
752
- <ScrollArea className="flex-1">
751
+ <div className="flex-1 min-h-0 overflow-y-auto">
753
752
  <div className="p-5 space-y-6">
754
753
 
755
754
  {/* Empty State */}
@@ -965,7 +964,7 @@ export default function ModelsView() {
965
964
  )}
966
965
  </div>
967
966
  </div>
968
- </ScrollArea>
967
+ </div>
969
968
  </div>
970
969
  );
971
970
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groove-dev",
3
- "version": "0.27.142",
3
+ "version": "0.27.143",
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)",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/cli",
3
- "version": "0.27.142",
3
+ "version": "0.27.143",
4
4
  "description": "GROOVE CLI — manage AI coding agents from your terminal",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/daemon",
3
- "version": "0.27.142",
3
+ "version": "0.27.143",
4
4
  "description": "GROOVE daemon — agent orchestration engine",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",