groove-dev 0.26.16 → 0.26.18

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.
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <link rel="icon" type="image/png" href="/favicon.png" />
7
7
  <title>Groove GUI</title>
8
- <script type="module" crossorigin src="/assets/index-7eQvV_N9.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-aT3nOZj0.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/vendor-C0HXlhrU.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/reactflow-BQPfi37R.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/codemirror-BBL3i_JW.js">
@@ -259,9 +259,11 @@ export const useGrooveStore = create((set, get) => ({
259
259
  set((s) => {
260
260
  const chatHistory = { ...s.chatHistory };
261
261
  const tokenTimeline = { ...s.tokenTimeline };
262
+ const activityLog = { ...s.activityLog };
262
263
  if (chatHistory[msg.oldAgentId]?.length) chatHistory[msg.newAgentId] = [...chatHistory[msg.oldAgentId]];
263
264
  if (tokenTimeline[msg.oldAgentId]?.length) tokenTimeline[msg.newAgentId] = [...tokenTimeline[msg.oldAgentId]];
264
- return { chatHistory, tokenTimeline, detailPanel: { type: 'agent', agentId: msg.newAgentId } };
265
+ if (activityLog[msg.oldAgentId]?.length) activityLog[msg.newAgentId] = [...activityLog[msg.oldAgentId]];
266
+ return { chatHistory, tokenTimeline, activityLog, detailPanel: { type: 'agent', agentId: msg.newAgentId } };
265
267
  });
266
268
  }
267
269
  break;
@@ -321,17 +321,21 @@ function AgentTreeInner() {
321
321
 
322
322
  useEffect(() => { setEdges(targetEdges); }, [targetEdges, setEdges]);
323
323
 
324
- // Only fitView when agents are added — not on metric/token/chat updates
324
+ // Only fitView when agents are added — debounced so team launches (multiple spawns)
325
+ // don't cause repeated zoom/pan jitter
325
326
  const agentIdStr = agents.map((a) => a.id).join(',');
327
+ const fitTimer = useRef(null);
326
328
  useEffect(() => {
327
329
  const currentIds = new Set(agents.map((a) => a.id));
328
330
  const isNewAgent = agents.length > 0 && [...currentIds].some((id) => !prevAgentIds.current.has(id));
329
331
  prevAgentIds.current = currentIds;
330
332
 
331
333
  if (prevCount === 0 && agents.length > 0) {
332
- setTimeout(() => fitView({ padding: 0.3, maxZoom: 1.2, duration: 0 }), 50);
334
+ fitView({ padding: 0.3, maxZoom: 1.2, duration: 0 });
333
335
  } else if (isNewAgent) {
334
- setTimeout(() => fitView({ padding: 0.3, maxZoom: 1.2, duration: 300 }), 100);
336
+ // Debounce: wait 500ms for batch spawns to settle before fitting
337
+ clearTimeout(fitTimer.current);
338
+ fitTimer.current = setTimeout(() => fitView({ padding: 0.3, maxZoom: 1.2, duration: 300 }), 500);
335
339
  }
336
340
  setPrevCount(agents.length);
337
341
  }, [agentIdStr, prevCount, fitView]); // eslint-disable-line react-hooks/exhaustive-deps
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groove-dev",
3
- "version": "0.26.16",
3
+ "version": "0.26.18",
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)",
@@ -1768,7 +1768,17 @@ Keep responses concise. Help them think, don't lecture them about the system the
1768
1768
  }
1769
1769
 
1770
1770
  const baseDir = daemon.config?.defaultWorkingDir || daemon.projectDir;
1771
- const defaultTeamId = daemon.teams.getDefault()?.id || null;
1771
+
1772
+ // Use the planner's teamId so launched agents join the correct team.
1773
+ // Accept explicit teamId from request body, or find the most recent planner agent.
1774
+ let launchTeamId = req.body?.teamId || null;
1775
+ if (!launchTeamId) {
1776
+ const planners = daemon.registry.getAll()
1777
+ .filter((a) => a.role === 'planner')
1778
+ .sort((a, b) => (b.lastActivity || '').localeCompare(a.lastActivity || ''));
1779
+ launchTeamId = planners[0]?.teamId || null;
1780
+ }
1781
+ const defaultTeamId = launchTeamId || daemon.teams.getDefault()?.id || null;
1772
1782
 
1773
1783
  // If planner specified a project directory, create it and use it as workingDir
1774
1784
  let projectWorkingDir = baseDir;
@@ -368,6 +368,12 @@ For normal file edits within your scope, proceed without review.
368
368
  if (this.daemon.integrations) this.daemon.integrations.refreshMcpJson();
369
369
  if (status === 'completed' && this.daemon.journalist) this.daemon.journalist.cycle().catch(() => {});
370
370
  this._checkPhase2(agent.id);
371
+
372
+ // Auto-trigger idle QC in the same team
373
+ if (status === 'completed') {
374
+ const files = this.daemon.journalist?.getAgentFiles(agent) || [];
375
+ if (files.length > 0) this._triggerIdleQC(agent);
376
+ }
371
377
  });
372
378
 
373
379
  // Wire errors — broadcast to GUI for display
@@ -561,6 +567,13 @@ For normal file edits within your scope, proceed without review.
561
567
 
562
568
  // Phase 2 auto-spawn: check if all phase 1 agents for a team are done
563
569
  this._checkPhase2(agent.id);
570
+
571
+ // Auto-trigger idle QC: if this agent modified files and there's an idle QC
572
+ // in the same team, activate it to verify the changes
573
+ if (finalStatus === 'completed') {
574
+ const files = this.daemon.journalist?.getAgentFiles(agent) || [];
575
+ if (files.length > 0) this._triggerIdleQC(agent);
576
+ }
564
577
  });
565
578
 
566
579
  proc.on('error', (err) => {
@@ -660,8 +673,20 @@ For normal file edits within your scope, proceed without review.
660
673
  // Remove from pending
661
674
  pending.splice(i, 1);
662
675
 
663
- // Auto-spawn phase 2 agents
676
+ // Check if phase 1 agents did any real work by looking at file modifications.
677
+ // If no agent modified any files, there's nothing to QC.
678
+ const journalist = this.daemon.journalist;
679
+ const phase1Idle = group.waitFor.every((id) => {
680
+ const a = registry.get(id);
681
+ if (!a) return true;
682
+ const files = journalist?.getAgentFiles(a) || [];
683
+ return files.length === 0;
684
+ });
685
+
686
+ // Auto-spawn phase 2 agents — if phase 1 was idle, clear the prompt
687
+ // so QC also waits for instructions instead of auditing nothing
664
688
  for (const config of group.agents) {
689
+ if (phase1Idle) config.prompt = '';
665
690
  try {
666
691
  const validated = validateAgentConfig(config);
667
692
  if (!validated.teamId) validated.teamId = this.daemon.teams.getDefault()?.id || null;
@@ -694,6 +719,48 @@ For normal file edits within your scope, proceed without review.
694
719
  }
695
720
  }
696
721
 
722
+ /**
723
+ * Auto-trigger an idle QC agent in the same team when a teammate completes real work.
724
+ * "Idle" = running fullstack agent that hasn't modified any files yet.
725
+ */
726
+ _triggerIdleQC(completedAgent) {
727
+ const registry = this.daemon.registry;
728
+ if (!completedAgent.teamId) return;
729
+
730
+ // Find a running fullstack/QC agent in the same team that's idle (no files modified)
731
+ const journalist = this.daemon.journalist;
732
+ const qc = registry.getAll().find((a) =>
733
+ a.id !== completedAgent.id &&
734
+ a.teamId === completedAgent.teamId &&
735
+ a.role === 'fullstack' &&
736
+ a.status === 'running' &&
737
+ (journalist?.getAgentFiles(a) || []).length === 0
738
+ );
739
+ if (!qc) return;
740
+
741
+ // Gather context about what the completed agent did
742
+ const files = this.daemon.journalist?.getAgentFiles(completedAgent) || [];
743
+ const result = this.daemon.journalist?.getAgentResult(completedAgent) || '';
744
+ const fileList = files.length > 0 ? `\nFiles modified: ${files.slice(0, 20).join(', ')}` : '';
745
+
746
+ const message = `Your teammate ${completedAgent.name} (${completedAgent.role}) just finished their work.${fileList}${result ? `\n\nTheir summary:\n${result.slice(0, 2000)}` : ''}\n\nPlease audit their changes: verify correctness, check for bugs, run tests if available, and report any issues.`;
747
+
748
+ // Send message to the QC agent via the instruct flow
749
+ this.sendMessage(qc.id, message).catch((err) => {
750
+ console.error(`[Groove] QC auto-trigger failed: ${err.message}`);
751
+ });
752
+
753
+ this.daemon.audit.log('qc.autoTrigger', {
754
+ qcId: qc.id, qcName: qc.name,
755
+ triggeredBy: completedAgent.name, role: completedAgent.role,
756
+ });
757
+ this.daemon.broadcast({
758
+ type: 'qc:triggered',
759
+ qcId: qc.id, qcName: qc.name,
760
+ triggeredBy: completedAgent.name,
761
+ });
762
+ }
763
+
697
764
  /**
698
765
  * Resume a completed agent's session with a new message.
699
766
  * Uses --resume SESSION_ID for zero cold-start continuation.