groove-dev 0.27.145 → 0.27.146

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 (74) hide show
  1. package/CLAUDE.md +7 -0
  2. package/node_modules/@groove-dev/cli/package.json +1 -1
  3. package/node_modules/@groove-dev/daemon/package.json +1 -1
  4. package/node_modules/@groove-dev/daemon/src/api.js +12 -6
  5. package/node_modules/@groove-dev/daemon/src/conversations.js +41 -10
  6. package/node_modules/@groove-dev/daemon/src/introducer.js +20 -0
  7. package/node_modules/@groove-dev/daemon/src/process.js +262 -15
  8. package/node_modules/@groove-dev/daemon/src/providers/groove-network.js +1 -3
  9. package/node_modules/@groove-dev/daemon/src/rotator.js +15 -3
  10. package/node_modules/@groove-dev/daemon/src/routes/agents.js +43 -0
  11. package/node_modules/@groove-dev/daemon/templates/lab-general.json +12 -0
  12. package/node_modules/@groove-dev/daemon/templates/llama-cpp-setup.json +12 -0
  13. package/node_modules/@groove-dev/gui/dist/assets/index-BKbsE_hn.js +1011 -0
  14. package/node_modules/@groove-dev/gui/dist/assets/index-CEkPsSAm.css +1 -0
  15. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  16. package/node_modules/@groove-dev/gui/package.json +1 -1
  17. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +132 -4
  18. package/node_modules/@groove-dev/gui/src/components/chat/chat-header.jsx +1 -8
  19. package/node_modules/@groove-dev/gui/src/components/chat/chat-input.jsx +135 -13
  20. package/node_modules/@groove-dev/gui/src/components/chat/chat-messages.jsx +21 -4
  21. package/node_modules/@groove-dev/gui/src/components/chat/chat-view.jsx +6 -5
  22. package/node_modules/@groove-dev/gui/src/components/chat/model-picker.jsx +3 -3
  23. package/node_modules/@groove-dev/gui/src/components/lab/chat-playground.jsx +3 -3
  24. package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +9 -3
  25. package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +13 -3
  26. package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +5 -5
  27. package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +1 -3
  28. package/node_modules/@groove-dev/gui/src/components/layout/app-shell.jsx +2 -0
  29. package/node_modules/@groove-dev/gui/src/components/layout/status-bar.jsx +24 -1
  30. package/node_modules/@groove-dev/gui/src/components/ui/question-modal.jsx +107 -0
  31. package/node_modules/@groove-dev/gui/src/components/ui/sheet.jsx +2 -2
  32. package/node_modules/@groove-dev/gui/src/stores/groove.js +32 -2
  33. package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +10 -1
  34. package/node_modules/@groove-dev/gui/src/stores/slices/chat-slice.js +1 -0
  35. package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +27 -22
  36. package/package.json +1 -1
  37. package/packages/cli/package.json +1 -1
  38. package/packages/daemon/package.json +1 -1
  39. package/packages/daemon/src/api.js +12 -6
  40. package/packages/daemon/src/conversations.js +41 -10
  41. package/packages/daemon/src/introducer.js +20 -0
  42. package/packages/daemon/src/process.js +262 -15
  43. package/packages/daemon/src/providers/groove-network.js +1 -3
  44. package/packages/daemon/src/rotator.js +15 -3
  45. package/packages/daemon/src/routes/agents.js +43 -0
  46. package/packages/daemon/templates/lab-general.json +12 -0
  47. package/packages/daemon/templates/llama-cpp-setup.json +12 -0
  48. package/packages/gui/dist/assets/index-BKbsE_hn.js +1011 -0
  49. package/packages/gui/dist/assets/index-CEkPsSAm.css +1 -0
  50. package/packages/gui/dist/index.html +2 -2
  51. package/packages/gui/package.json +1 -1
  52. package/packages/gui/src/components/agents/spawn-wizard.jsx +132 -4
  53. package/packages/gui/src/components/chat/chat-header.jsx +1 -8
  54. package/packages/gui/src/components/chat/chat-input.jsx +135 -13
  55. package/packages/gui/src/components/chat/chat-messages.jsx +21 -4
  56. package/packages/gui/src/components/chat/chat-view.jsx +6 -5
  57. package/packages/gui/src/components/chat/model-picker.jsx +3 -3
  58. package/packages/gui/src/components/lab/chat-playground.jsx +3 -3
  59. package/packages/gui/src/components/lab/lab-assistant.jsx +9 -3
  60. package/packages/gui/src/components/lab/metrics-panel.jsx +13 -3
  61. package/packages/gui/src/components/lab/parameter-panel.jsx +5 -5
  62. package/packages/gui/src/components/lab/runtime-config.jsx +1 -3
  63. package/packages/gui/src/components/layout/app-shell.jsx +2 -0
  64. package/packages/gui/src/components/layout/status-bar.jsx +24 -1
  65. package/packages/gui/src/components/ui/question-modal.jsx +107 -0
  66. package/packages/gui/src/components/ui/sheet.jsx +2 -2
  67. package/packages/gui/src/stores/groove.js +32 -2
  68. package/packages/gui/src/stores/slices/agents-slice.js +10 -1
  69. package/packages/gui/src/stores/slices/chat-slice.js +1 -0
  70. package/packages/gui/src/views/model-lab.jsx +27 -22
  71. package/node_modules/@groove-dev/gui/dist/assets/index-Bxc0gU06.js +0 -1006
  72. package/node_modules/@groove-dev/gui/dist/assets/index-C0pztKBn.css +0 -1
  73. package/packages/gui/dist/assets/index-Bxc0gU06.js +0 -1006
  74. package/packages/gui/dist/assets/index-C0pztKBn.css +0 -1
@@ -314,6 +314,11 @@ export const useGrooveStore = create((set, get) => ({
314
314
  }
315
315
 
316
316
  case 'agent:exit': {
317
+ if (msg.status === 'waiting_for_input') {
318
+ const waitAgent = get().agents.find((a) => a.id === msg.agentId);
319
+ get().addToast('info', `${waitAgent?.name || msg.agentId.slice(0, 8)} needs your input`, 'Check the question popup below');
320
+ break;
321
+ }
317
322
  const agent = get().agents.find((a) => a.id === msg.agentId);
318
323
  const name = agent?.name || msg.agentId;
319
324
  const isKill = msg.status === 'killed' || msg.code === 143 || msg.code === 137;
@@ -415,6 +420,14 @@ export const useGrooveStore = create((set, get) => ({
415
420
  get().addChatMessage(msg.agentId, 'system', 'Agent is working — message will be delivered when it finishes.');
416
421
  break;
417
422
 
423
+ case 'agent:question':
424
+ set((s) => ({ pendingQuestions: [...s.pendingQuestions, msg.data] }));
425
+ break;
426
+
427
+ case 'agent:question:resolved':
428
+ set((s) => ({ pendingQuestions: s.pendingQuestions.filter((q) => q.agentId !== msg.agentId) }));
429
+ break;
430
+
418
431
  case 'ollama:pull:progress':
419
432
  set({ ollamaPullProgress: { ...get().ollamaPullProgress, [msg.model]: { status: 'pulling', progress: msg.progress } } });
420
433
  break;
@@ -450,6 +463,8 @@ export const useGrooveStore = create((set, get) => ({
450
463
  case 'lab:runtime:added':
451
464
  case 'lab:runtime:updated':
452
465
  case 'lab:runtime:removed':
466
+ case 'lab:runtime:started':
467
+ case 'lab:runtime:stopped':
453
468
  case 'llama:server:stopped':
454
469
  get().fetchLabRuntimes();
455
470
  break;
@@ -888,6 +903,17 @@ export const useGrooveStore = create((set, get) => ({
888
903
  break;
889
904
  }
890
905
 
906
+ case 'conversation:tool': {
907
+ const { conversationId, name, summary } = msg.data || msg;
908
+ if (!conversationId) break;
909
+ set((s) => {
910
+ const tools = { ...s.conversationActiveTools };
911
+ tools[conversationId] = { name: name || 'Tool', summary: summary || null, timestamp: Date.now() };
912
+ return { conversationActiveTools: tools };
913
+ });
914
+ break;
915
+ }
916
+
891
917
  case 'conversation:chunk': {
892
918
  const { conversationId, text } = msg.data || msg;
893
919
  if (!conversationId || !text) break;
@@ -902,7 +928,9 @@ export const useGrooveStore = create((set, get) => ({
902
928
  arr.push({ from: 'assistant', text, timestamp: Date.now() });
903
929
  }
904
930
  msgs[conversationId] = arr.slice(-200);
905
- return { conversationMessages: msgs, streamingConversationId: conversationId };
931
+ const tools = { ...s.conversationActiveTools };
932
+ delete tools[conversationId];
933
+ return { conversationMessages: msgs, streamingConversationId: conversationId, conversationActiveTools: tools };
906
934
  });
907
935
  break;
908
936
  }
@@ -910,7 +938,9 @@ export const useGrooveStore = create((set, get) => ({
910
938
  case 'conversation:complete': {
911
939
  const { conversationId } = msg.data || msg;
912
940
  if (conversationId && get().streamingConversationId === conversationId) {
913
- set({ sendingMessage: false, streamingConversationId: null });
941
+ const tools = { ...get().conversationActiveTools };
942
+ delete tools[conversationId];
943
+ set({ sendingMessage: false, streamingConversationId: null, conversationActiveTools: tools });
914
944
  }
915
945
  if (conversationId) persistJSON('groove:conversationMessages', get().conversationMessages);
916
946
  break;
@@ -14,6 +14,15 @@ export const createAgentsSlice = (set, get) => ({
14
14
  // Track which agents are thinking (sent a message, waiting for response)
15
15
  thinkingAgents: new Set(),
16
16
 
17
+ // Pending questions from agents awaiting user input
18
+ pendingQuestions: [],
19
+
20
+ answerQuestion(agentId, answers) {
21
+ return api.post(`/agents/${agentId}/answer`, { answers }).then(() => {
22
+ set((s) => ({ pendingQuestions: s.pendingQuestions.filter((q) => q.agentId !== agentId) }));
23
+ });
24
+ },
25
+
17
26
  // ── Workspace Mode ────────────────────────────────────────
18
27
  workspaceMode: localStorage.getItem('groove:workspaceMode') === 'true',
19
28
  workspaceAgentId: null,
@@ -38,7 +47,7 @@ export const createAgentsSlice = (set, get) => ({
38
47
 
39
48
  async spawnAgent(config) {
40
49
  try {
41
- const teamId = get().activeTeamId;
50
+ const teamId = config.teamId || get().activeTeamId;
42
51
  const agent = await api.post('/agents', { ...config, teamId });
43
52
  get().addToast('success', `Spawned ${agent.name}`);
44
53
  return agent;
@@ -10,6 +10,7 @@ export const createChatSlice = (set, get) => ({
10
10
  conversations: [],
11
11
  activeConversationId: localStorage.getItem('groove:activeConversationId') || null,
12
12
  conversationMessages: loadJSON('groove:conversationMessages'),
13
+ conversationActiveTools: {},
13
14
  sendingMessage: false,
14
15
  streamingConversationId: null,
15
16
  conversationRoles: loadJSON('groove:conversationRoles'),
@@ -135,6 +135,7 @@ export default function ModelLabView() {
135
135
  const labAssistantAgentId = useGrooveStore((s) => s.labAssistantAgentId);
136
136
  const labAssistantMode = useGrooveStore((s) => s.labAssistantMode);
137
137
  const setLabAssistantMode = useGrooveStore((s) => s.setLabAssistantMode);
138
+ const launchLabAssistant = useGrooveStore((s) => s.launchLabAssistant);
138
139
 
139
140
  useEffect(() => { fetchLabRuntimes(); }, [fetchLabRuntimes]);
140
141
 
@@ -234,28 +235,32 @@ export default function ModelLabView() {
234
235
  {leftCollapsed && (
235
236
  <PanelToggle collapsed onClick={() => setLeftCollapsed(false)} side="left" />
236
237
  )}
237
- {labAssistantAgentId && (
238
- <div className="flex items-center gap-px bg-surface-2 rounded p-px">
239
- <button
240
- onClick={() => setLabAssistantMode(false)}
241
- className={cn(
242
- 'px-3 py-1 text-2xs font-sans font-medium rounded-sm transition-colors cursor-pointer',
243
- !labAssistantMode ? 'text-text-0 bg-surface-4' : 'text-text-3 hover:text-text-1',
244
- )}
245
- >
246
- Playground
247
- </button>
248
- <button
249
- onClick={() => setLabAssistantMode(true)}
250
- className={cn(
251
- 'px-3 py-1 text-2xs font-sans font-medium rounded-sm transition-colors cursor-pointer',
252
- labAssistantMode ? 'text-text-0 bg-surface-4' : 'text-text-3 hover:text-text-1',
253
- )}
254
- >
255
- Assistant
256
- </button>
257
- </div>
258
- )}
238
+ <div className="flex items-center gap-px bg-surface-2 rounded p-px">
239
+ <button
240
+ onClick={() => setLabAssistantMode(false)}
241
+ className={cn(
242
+ 'px-3 py-1 text-2xs font-sans font-medium rounded-sm transition-colors cursor-pointer',
243
+ !labAssistantMode ? 'text-text-0 bg-surface-4' : 'text-text-3 hover:text-text-1',
244
+ )}
245
+ >
246
+ Playground
247
+ </button>
248
+ <button
249
+ onClick={() => {
250
+ if (labAssistantAgentId) {
251
+ setLabAssistantMode(true);
252
+ } else {
253
+ launchLabAssistant('lab-general');
254
+ }
255
+ }}
256
+ className={cn(
257
+ 'px-3 py-1 text-2xs font-sans font-medium rounded-sm transition-colors cursor-pointer',
258
+ labAssistantMode ? 'text-text-0 bg-surface-4' : 'text-text-3 hover:text-text-1',
259
+ )}
260
+ >
261
+ Assistant
262
+ </button>
263
+ </div>
259
264
  <div className="flex-1" />
260
265
  {rightCollapsed && (
261
266
  <PanelToggle collapsed onClick={() => setRightCollapsed(false)} side="right" />
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groove-dev",
3
- "version": "0.27.145",
3
+ "version": "0.27.146",
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.145",
3
+ "version": "0.27.146",
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.145",
3
+ "version": "0.27.146",
4
4
  "description": "GROOVE daemon — agent orchestration engine",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -11,7 +11,7 @@ import { hostname, networkInterfaces, homedir } from 'os';
11
11
  import { StringDecoder } from 'string_decoder';
12
12
  import { request as httpRequest } from 'http';
13
13
  import { lookup as mimeLookup } from './mimetypes.js';
14
- import { listProviders, getProvider, clearInstallCache, getProviderMetadata, getProviderPath, setProviderPaths } from './providers/index.js';
14
+ import { listProviders, getProvider, clearInstallCache, getProviderMetadata, getProviderPath, setProviderPaths, isProviderInstalled } from './providers/index.js';
15
15
  import { OllamaProvider } from './providers/ollama.js';
16
16
  import { ClaudeCodeProvider } from './providers/claude-code.js';
17
17
  import { supportsSignalFlag, compareSemver, parseSemver } from './providers/groove-network.js';
@@ -1336,7 +1336,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
1336
1336
 
1337
1337
  app.post('/api/onboarding/set-default', async (req, res) => {
1338
1338
  const { provider, model } = req.body;
1339
- const validProviders = ['claude-code', 'codex', 'gemini', 'ollama'];
1339
+ const validProviders = ['claude-code', 'codex', 'gemini', 'grok', 'ollama', 'local'];
1340
1340
  if (!provider || !validProviders.includes(provider)) {
1341
1341
  return res.status(400).json({ error: `Invalid provider. Valid: ${validProviders.join(', ')}` });
1342
1342
  }
@@ -1721,8 +1721,9 @@ Keep responses concise. Help them think, don't lecture them about the system the
1721
1721
  app.post('/api/lab/assistant', async (req, res) => {
1722
1722
  try {
1723
1723
  const { backend, model } = req.body || {};
1724
- if (!backend || !['vllm', 'tgi', 'mlx'].includes(backend)) {
1725
- return res.status(400).json({ error: 'backend must be "vllm", "tgi", or "mlx"' });
1724
+ const validBackends = ['vllm', 'tgi', 'mlx', 'llama-cpp', 'lab-general'];
1725
+ if (!backend || !validBackends.includes(backend)) {
1726
+ return res.status(400).json({ error: `backend must be one of: ${validBackends.join(', ')}` });
1726
1727
  }
1727
1728
  const templatePath = resolve(__dirname, `../templates/${backend}-setup.json`);
1728
1729
  const template = JSON.parse(readFileSync(templatePath, 'utf8'));
@@ -1733,14 +1734,19 @@ Keep responses concise. Help them think, don't lecture them about the system the
1733
1734
  const desc = parts.join(', ');
1734
1735
  prompt = `The user has selected a local model: ${desc} (id: ${model.id}).\nUse this model for setup instead of recommending a different one. If this exact model isn't available in the runtime's format, find the closest equivalent (same base model, similar quantization).\n\n${prompt}`;
1735
1736
  }
1737
+ // Pick best available CLI provider: prefer user's default, fall back through tool-use capable providers
1738
+ const cliProviders = ['claude-code', 'codex', 'gemini'];
1739
+ const defaultProv = daemon.config.defaultProvider;
1740
+ let assistantProvider = cliProviders.includes(defaultProv) && isProviderInstalled(defaultProv)
1741
+ ? defaultProv
1742
+ : cliProviders.find((p) => isProviderInstalled(p)) || 'claude-code';
1736
1743
  const config = {
1737
1744
  role: 'lab-assistant',
1738
1745
  scope: agentConfig.scope || [],
1739
- provider: agentConfig.provider || daemon.config.defaultProvider,
1746
+ provider: assistantProvider,
1740
1747
  prompt,
1741
1748
  metadata: { labAssistant: true, backend },
1742
1749
  };
1743
- if (!config.provider) config.provider = daemon.config.defaultProvider;
1744
1750
  const agent = await daemon.processes.spawn(config);
1745
1751
  daemon.audit.log('lab.assistant.spawn', { id: agent.id, backend });
1746
1752
  res.status(201).json({ agentId: agent.id, backend });
@@ -475,31 +475,62 @@ export class ConversationManager {
475
475
 
476
476
  try {
477
477
  const json = JSON.parse(trimmed);
478
+ let matched = false;
478
479
  if (json.type === 'assistant' && json.message?.content) {
479
480
  for (const block of json.message.content) {
480
481
  if (block.type === 'text' && block.text) {
481
482
  emitChunk(block.text);
482
483
  }
483
484
  }
484
- continue;
485
+ matched = true;
485
486
  }
486
- if (json.type === 'content_block_delta' && json.delta?.text) {
487
+ if (!matched && json.type === 'content_block_delta' && json.delta?.text) {
487
488
  emitChunk(json.delta.text);
488
- continue;
489
+ matched = true;
489
490
  }
490
- if (json.type === 'result' && json.result) continue;
491
- if (json.type === 'token' && json.text != null) {
491
+ if (!matched && json.type === 'result' && json.result) {
492
+ matched = true;
493
+ }
494
+ if (!matched && json.type === 'token' && json.text != null) {
492
495
  emitChunk(json.text);
493
- continue;
496
+ matched = true;
494
497
  }
495
- if ((json.type === 'done' || json.type === 'complete' || json.type === 'result') && json.text) {
498
+ if (!matched && (json.type === 'done' || json.type === 'complete' || json.type === 'result') && json.text) {
496
499
  emitChunk(json.text);
497
- continue;
500
+ matched = true;
498
501
  }
499
- if (json.content?.[0]?.text) {
502
+ if (!matched && json.content?.[0]?.text) {
500
503
  emitChunk(json.content[0].text);
501
- continue;
504
+ matched = true;
505
+ }
506
+ // Fallback: use provider's parseOutput for provider-specific formats (Gemini tool_use, tool_result, Codex items, etc.)
507
+ if (!matched && provider.parseOutput) {
508
+ const parsed = provider.parseOutput(trimmed);
509
+ if (parsed) {
510
+ const blocks = Array.isArray(parsed.data) ? parsed.data : [];
511
+ for (const block of blocks) {
512
+ if (block.type === 'text' && block.text) {
513
+ emitChunk(block.text);
514
+ } else if (block.type === 'tool_use') {
515
+ const cmd = block.input?.command;
516
+ const path = block.input?.path;
517
+ const summary = cmd || path || (block.input && Object.keys(block.input).length > 0
518
+ ? Object.values(block.input)[0]
519
+ : null);
520
+ if (block.name || summary) {
521
+ this.daemon.broadcast({
522
+ type: 'conversation:tool',
523
+ data: { conversationId: id, name: block.name || 'Tool', summary: summary ? String(summary).slice(0, 120) : null },
524
+ });
525
+ }
526
+ }
527
+ }
528
+ if (!blocks.length && typeof parsed.data === 'string' && parsed.data) {
529
+ emitChunk(parsed.data);
530
+ }
531
+ }
502
532
  }
533
+ continue;
503
534
  } catch { /* not JSON */ }
504
535
 
505
536
  if (!trimmed.startsWith('{')) {
@@ -106,6 +106,26 @@ export class Introducer {
106
106
 
107
107
  if (others.length === 0) {
108
108
  lines.push('You are the only agent on this project right now.');
109
+
110
+ // Solo agents get full authority — no team coordination, no scope limits.
111
+ // Business roles and planners keep their restrictions (intentional by design).
112
+ const NO_SOLO_EXPAND = new Set([
113
+ 'planner', 'cmo', 'cfo', 'ea', 'support', 'analyst', 'home', 'chat', 'ambassador',
114
+ ]);
115
+ if (!NO_SOLO_EXPAND.has(newAgent.role)) {
116
+ lines.push('');
117
+ lines.push('## Solo Mode');
118
+ lines.push('');
119
+ lines.push('You are working alone — no team, no scope restrictions, no coordination needed. You have full authority to do whatever the task requires:');
120
+ lines.push('- Install dependencies (npm install, pip install, etc.)');
121
+ lines.push('- Start dev servers and long-running processes when needed');
122
+ lines.push('- Run tests, builds, and linters');
123
+ lines.push('- Create, modify, or delete any project files');
124
+ lines.push('- Commit and manage git operations');
125
+ lines.push('- Perform any shell commands necessary to complete your task');
126
+ lines.push('');
127
+ lines.push('You are not limited to your role\'s typical focus area. If the task requires work outside your specialty, handle it directly.');
128
+ }
109
129
  } else {
110
130
  lines.push(`## Team (${others.length} other agent${others.length > 1 ? 's' : ''})`);
111
131
  lines.push('');