groove-dev 0.27.60 → 0.27.62

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 (45) 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 +167 -60
  4. package/node_modules/@groove-dev/daemon/src/conversations.js +75 -31
  5. package/node_modules/@groove-dev/daemon/src/journalist.js +1 -0
  6. package/node_modules/@groove-dev/daemon/src/process.js +17 -7
  7. package/node_modules/@groove-dev/daemon/src/providers/base.js +4 -0
  8. package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +63 -0
  9. package/node_modules/@groove-dev/daemon/src/providers/codex.js +55 -0
  10. package/node_modules/@groove-dev/daemon/src/providers/gemini.js +53 -0
  11. package/node_modules/@groove-dev/daemon/src/providers/local.js +44 -0
  12. package/node_modules/@groove-dev/daemon/src/providers/ollama.js +44 -0
  13. package/node_modules/@groove-dev/daemon/src/rotator.js +4 -0
  14. package/node_modules/@groove-dev/gui/dist/assets/{index-DD6taBMp.css → index-B3AqeyS4.css} +1 -1
  15. package/node_modules/@groove-dev/gui/dist/assets/{index-DcnRqlqB.js → index-Dvum7uoe.js} +178 -178
  16. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  17. package/node_modules/@groove-dev/gui/package.json +1 -1
  18. package/node_modules/@groove-dev/gui/src/components/chat/chat-view.jsx +3 -2
  19. package/node_modules/@groove-dev/gui/src/components/chat/model-picker.jsx +1 -1
  20. package/node_modules/@groove-dev/gui/src/components/layout/status-bar.jsx +13 -7
  21. package/node_modules/@groove-dev/gui/src/components/ui/update-modal.jsx +70 -0
  22. package/node_modules/@groove-dev/gui/src/stores/groove.js +52 -12
  23. package/package.json +1 -1
  24. package/packages/cli/package.json +1 -1
  25. package/packages/daemon/package.json +1 -1
  26. package/packages/daemon/src/api.js +167 -60
  27. package/packages/daemon/src/conversations.js +75 -31
  28. package/packages/daemon/src/journalist.js +1 -0
  29. package/packages/daemon/src/process.js +17 -7
  30. package/packages/daemon/src/providers/base.js +4 -0
  31. package/packages/daemon/src/providers/claude-code.js +63 -0
  32. package/packages/daemon/src/providers/codex.js +55 -0
  33. package/packages/daemon/src/providers/gemini.js +53 -0
  34. package/packages/daemon/src/providers/local.js +44 -0
  35. package/packages/daemon/src/providers/ollama.js +44 -0
  36. package/packages/daemon/src/rotator.js +4 -0
  37. package/packages/gui/dist/assets/{index-DD6taBMp.css → index-B3AqeyS4.css} +1 -1
  38. package/packages/gui/dist/assets/{index-DcnRqlqB.js → index-Dvum7uoe.js} +178 -178
  39. package/packages/gui/dist/index.html +2 -2
  40. package/packages/gui/package.json +1 -1
  41. package/packages/gui/src/components/chat/chat-view.jsx +3 -2
  42. package/packages/gui/src/components/chat/model-picker.jsx +1 -1
  43. package/packages/gui/src/components/layout/status-bar.jsx +13 -7
  44. package/packages/gui/src/components/ui/update-modal.jsx +70 -0
  45. package/packages/gui/src/stores/groove.js +52 -12
@@ -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-DcnRqlqB.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-Dvum7uoe.js"></script>
10
10
  <link rel="modulepreload" crossorigin href="/assets/vendor-C0HXlhrU.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/reactflow-BQPfi37R.js">
12
12
  <link rel="modulepreload" crossorigin href="/assets/codemirror-BBL3i_JW.js">
13
13
  <link rel="modulepreload" crossorigin href="/assets/xterm--7_ns2zW.js">
14
- <link rel="stylesheet" crossorigin href="/assets/index-DD6taBMp.css">
14
+ <link rel="stylesheet" crossorigin href="/assets/index-B3AqeyS4.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.60",
3
+ "version": "0.27.62",
4
4
  "description": "GROOVE GUI — visual agent control plane",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -38,6 +38,7 @@ export function ChatView() {
38
38
  const stopAgent = useGrooveStore((s) => s.stopAgent);
39
39
  const stopChatStreaming = useGrooveStore((s) => s.stopChatStreaming);
40
40
  const setConversationMode = useGrooveStore((s) => s.setConversationMode);
41
+ const setConversationModel = useGrooveStore((s) => s.setConversationModel);
41
42
 
42
43
  const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
43
44
 
@@ -74,11 +75,11 @@ export function ChatView() {
74
75
 
75
76
  const handleModelChange = useCallback(async (selection) => {
76
77
  if (activeConversationId) {
77
- // TODO: Update conversation model via API
78
+ await setConversationModel(activeConversationId, selection.provider, selection.model);
78
79
  } else {
79
80
  await handleNewChat(selection.provider, selection.model);
80
81
  }
81
- }, [activeConversationId, handleNewChat]);
82
+ }, [activeConversationId, setConversationModel, handleNewChat]);
82
83
 
83
84
  const currentModel = activeConversation
84
85
  ? { provider: activeConversation.provider, model: activeConversation.model }
@@ -83,7 +83,7 @@ export function ModelPicker({ value, onChange, disabled }) {
83
83
  </button>
84
84
 
85
85
  {open && (
86
- <div className="absolute top-full left-0 mt-1 w-72 max-h-80 overflow-y-auto rounded-lg border border-border bg-surface-1 shadow-xl z-50">
86
+ <div className="absolute top-full right-0 mt-1 w-72 max-h-80 overflow-y-auto rounded-lg border border-border bg-surface-1 shadow-xl z-50">
87
87
  {providers.length === 0 && (
88
88
  <div className="px-4 py-6 text-center text-xs text-text-3 font-sans">No providers available</div>
89
89
  )}
@@ -2,9 +2,11 @@
2
2
  import { Terminal, BookOpen, Radio, Plug, Globe, ArrowUpCircle, X, Unplug } from 'lucide-react';
3
3
  import { cn } from '../../lib/cn';
4
4
  import { StatusDot } from '../ui/status-dot';
5
+ import { Badge } from '../ui/badge';
5
6
  import { fmtUptime } from '../../lib/format';
6
7
  import { useGrooveStore } from '../../stores/groove';
7
8
  import { isElectron, openExternal } from '../../lib/electron';
9
+ import { UpdateModal } from '../ui/update-modal';
8
10
 
9
11
  export function StatusBar({
10
12
  connected,
@@ -18,7 +20,8 @@ export function StatusBar({
18
20
  const tunneled = useGrooveStore((s) => s.tunneled);
19
21
  const version = useGrooveStore((s) => s.version);
20
22
  const updateReady = useGrooveStore((s) => s.updateReady);
21
- const installUpdate = useGrooveStore((s) => s.installUpdate);
23
+ const updateProgress = useGrooveStore((s) => s.updateProgress);
24
+ const setUpdateModalOpen = useGrooveStore((s) => s.setUpdateModalOpen);
22
25
  const subscription = useGrooveStore((s) => s.subscription);
23
26
  const navigate = useGrooveStore((s) => s.setActiveView);
24
27
  const activeTunnel = savedTunnels.find((t) => t.active);
@@ -110,14 +113,16 @@ export function StatusBar({
110
113
  <div className="flex-1" />
111
114
 
112
115
  {/* Right: version + docs + terminal toggle */}
113
- {updateReady ? (
116
+ {updateReady || updateProgress ? (
114
117
  <button
115
- onClick={installUpdate}
116
- className="flex items-center gap-1.5 px-2 h-full text-success hover:bg-success/10 transition-colors cursor-pointer"
117
- title={`Update to v${updateReady}`}
118
+ onClick={() => setUpdateModalOpen(true)}
119
+ className="flex items-center gap-1 px-2 h-full cursor-pointer"
120
+ title={updateReady ? `Update to v${updateReady}` : 'Downloading update\u2026'}
118
121
  >
119
- <ArrowUpCircle size={12} />
120
- <span>v{updateReady}</span>
122
+ <Badge variant="warning" className="cursor-pointer">
123
+ <ArrowUpCircle size={10} />
124
+ {updateReady ? 'Update Available' : 'Downloading\u2026'}
125
+ </Badge>
121
126
  </button>
122
127
  ) : version ? (
123
128
  <span className="text-text-4 px-2">v{version}</span>
@@ -146,6 +151,7 @@ export function StatusBar({
146
151
  <span>Terminal</span>
147
152
  <kbd className="font-mono text-text-4 ml-0.5">Cmd+J</kbd>
148
153
  </button>
154
+ <UpdateModal />
149
155
  </footer>
150
156
  );
151
157
  }
@@ -0,0 +1,70 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+ import { ArrowUpCircle, Loader2 } from 'lucide-react';
3
+ import { Dialog, DialogContent } from './dialog';
4
+ import { Button } from './button';
5
+ import { useGrooveStore } from '../../stores/groove';
6
+
7
+ export function UpdateModal() {
8
+ const open = useGrooveStore((s) => s.updateModalOpen);
9
+ const setOpen = useGrooveStore((s) => s.setUpdateModalOpen);
10
+ const version = useGrooveStore((s) => s.version);
11
+ const updateReady = useGrooveStore((s) => s.updateReady);
12
+ const updateProgress = useGrooveStore((s) => s.updateProgress);
13
+ const installUpdate = useGrooveStore((s) => s.installUpdate);
14
+
15
+ const downloading = updateProgress && !updateReady;
16
+ const percent = downloading ? Math.max(0, Math.min(100, updateProgress.percent || 0)) : 100;
17
+
18
+ return (
19
+ <Dialog open={open} onOpenChange={setOpen}>
20
+ <DialogContent title="Update Available" description="Desktop app update">
21
+ <div className="px-5 py-4 flex flex-col gap-3">
22
+ <div className="flex items-center gap-3">
23
+ <div className="flex items-center justify-center w-10 h-10 rounded-lg bg-accent/12">
24
+ <ArrowUpCircle size={20} className="text-accent" />
25
+ </div>
26
+ <div>
27
+ <p className="text-sm text-text-1 font-sans font-medium">
28
+ {downloading ? 'Downloading update\u2026' : `Ready to update`}
29
+ </p>
30
+ <p className="text-xs text-text-3 font-sans mt-0.5">
31
+ {version && <span className="font-mono">{version}</span>}
32
+ {version && updateReady && ' \u2192 '}
33
+ {updateReady && <span className="font-mono text-accent">{updateReady}</span>}
34
+ </p>
35
+ </div>
36
+ </div>
37
+ {downloading && (
38
+ <div className="flex items-center gap-2 mt-1">
39
+ <Loader2 size={12} className="animate-spin text-accent flex-shrink-0" />
40
+ <div className="flex-1 h-1.5 rounded-full bg-surface-3 overflow-hidden">
41
+ <div
42
+ className="h-full rounded-full bg-accent transition-all duration-500 ease-out"
43
+ style={{ width: `${percent}%` }}
44
+ />
45
+ </div>
46
+ <span className="text-2xs font-mono text-text-3 tabular-nums">{percent}%</span>
47
+ </div>
48
+ )}
49
+ {!downloading && (
50
+ <p className="text-xs text-text-3 font-sans leading-relaxed">
51
+ The app will restart to apply the update. Your work is saved automatically.
52
+ </p>
53
+ )}
54
+ </div>
55
+ <div className="flex items-center justify-end gap-2 px-5 py-3 border-t border-border-subtle bg-surface-0">
56
+ <Button variant="ghost" size="sm" onClick={() => setOpen(false)}>Later</Button>
57
+ <Button
58
+ variant="primary"
59
+ size="sm"
60
+ disabled={downloading}
61
+ onClick={() => { installUpdate(); setOpen(false); }}
62
+ >
63
+ <ArrowUpCircle size={12} />
64
+ Update &amp; Restart
65
+ </Button>
66
+ </div>
67
+ </DialogContent>
68
+ </Dialog>
69
+ );
70
+ }
@@ -123,6 +123,8 @@ export const useGrooveStore = create((set, get) => ({
123
123
  // ── Version / Auto-Update ──────────────────────────────────
124
124
  version: null,
125
125
  updateReady: null,
126
+ updateProgress: null,
127
+ updateModalOpen: false,
126
128
 
127
129
  // ── Toasts ────────────────────────────────────────────────
128
130
  toasts: [],
@@ -184,10 +186,14 @@ export const useGrooveStore = create((set, get) => ({
184
186
  if (data) set({ subscription: { ...get().subscription, ...data } });
185
187
  });
186
188
  }
189
+ if (window.groove?.update?.onUpdateProgress) {
190
+ window.groove.update.onUpdateProgress((data) => {
191
+ set({ updateProgress: data });
192
+ });
193
+ }
187
194
  if (window.groove?.update?.onUpdateDownloaded) {
188
195
  window.groove.update.onUpdateDownloaded((data) => {
189
- set({ updateReady: data.version });
190
- get().addToast('info', 'Update available', `v${data.version} downloaded — restart to apply`);
196
+ set({ updateReady: data.version, updateModalOpen: true, updateProgress: null });
191
197
  });
192
198
  }
193
199
  };
@@ -861,7 +867,6 @@ export const useGrooveStore = create((set, get) => ({
861
867
  arr.push({ from: 'assistant', text, timestamp: Date.now() });
862
868
  }
863
869
  msgs[conversationId] = arr.slice(-200);
864
- persistJSON('groove:conversationMessages', msgs);
865
870
  return { conversationMessages: msgs, streamingConversationId: conversationId };
866
871
  });
867
872
  break;
@@ -869,10 +874,10 @@ export const useGrooveStore = create((set, get) => ({
869
874
 
870
875
  case 'conversation:complete': {
871
876
  const { conversationId } = msg.data || msg;
872
- if (conversationId) {
877
+ if (conversationId && get().streamingConversationId === conversationId) {
873
878
  set({ sendingMessage: false, streamingConversationId: null });
874
- persistJSON('groove:conversationMessages', get().conversationMessages);
875
879
  }
880
+ if (conversationId) persistJSON('groove:conversationMessages', get().conversationMessages);
876
881
  break;
877
882
  }
878
883
 
@@ -884,7 +889,8 @@ export const useGrooveStore = create((set, get) => ({
884
889
  if (!msgs[conversationId]) msgs[conversationId] = [];
885
890
  msgs[conversationId] = [...msgs[conversationId], { from: 'system', text: `Error: ${error || 'Unknown error'}`, timestamp: Date.now() }];
886
891
  persistJSON('groove:conversationMessages', msgs);
887
- return { conversationMessages: msgs, sendingMessage: false, streamingConversationId: null };
892
+ const isActive = s.streamingConversationId === conversationId;
893
+ return { conversationMessages: msgs, sendingMessage: isActive ? false : s.sendingMessage, streamingConversationId: isActive ? null : s.streamingConversationId };
888
894
  });
889
895
  }
890
896
  break;
@@ -1071,6 +1077,12 @@ export const useGrooveStore = create((set, get) => ({
1071
1077
  installUpdate() {
1072
1078
  window.groove?.update?.installUpdate();
1073
1079
  },
1080
+ setUpdateModalOpen(open) {
1081
+ set({ updateModalOpen: open });
1082
+ },
1083
+ checkForUpdate() {
1084
+ window.groove?.update?.checkForUpdate();
1085
+ },
1074
1086
 
1075
1087
  // ── Marketplace Auth ────────────────────────────────────────
1076
1088
 
@@ -1293,8 +1305,17 @@ export const useGrooveStore = create((set, get) => ({
1293
1305
  try {
1294
1306
  const result = await api.post('/recommended-team/launch', { teamId });
1295
1307
  const agents = result.agents || [];
1308
+ const failures = result.failed || [];
1296
1309
  const names = agents.map((a) => a.name).join(', ') || '';
1297
- get().addToast('success', 'Planner delegated work', names ? `→ ${names}` : undefined);
1310
+
1311
+ if (agents.length === 0 && failures.length > 0) {
1312
+ get().addToast('error', 'Delegation failed', failures.map(f => f.role + ': ' + f.error).join(', '));
1313
+ } else {
1314
+ get().addToast('success', 'Planner delegated work', names ? `→ ${names}` : undefined);
1315
+ if (failures.length > 0) {
1316
+ get().addToast('warning', `${failures.length} agent(s) failed to spawn`, failures.map(f => f.role + ': ' + f.error).join(', '));
1317
+ }
1318
+ }
1298
1319
  if (agents.length > 0) {
1299
1320
  set((s) => ({
1300
1321
  thinkingAgents: new Set([...s.thinkingAgents, ...agents.map((a) => a.id)]),
@@ -1325,11 +1346,21 @@ export const useGrooveStore = create((set, get) => ({
1325
1346
  get().addToast('info', 'Launching team...');
1326
1347
  const body = { ...(modifiedAgents && { agents: modifiedAgents }), ...(teamId && { teamId }) };
1327
1348
  const result = await api.post('/recommended-team/launch', body);
1328
- const sub = [
1329
- result.phase2Pending ? `${result.phase2Pending} QC queued` : '',
1330
- result.projectDir ? `→ ${result.projectDir}/` : '',
1331
- ].filter(Boolean).join(' · ');
1332
- get().addToast('success', `Launched ${(result.launched || 0) + (result.reused || 0)} agents`, sub || undefined);
1349
+ const totalOk = (result.launched || 0) + (result.reused || 0);
1350
+ const failures = result.failed || [];
1351
+
1352
+ if (totalOk === 0 && failures.length > 0) {
1353
+ get().addToast('error', 'Team launch failed', failures.map(f => f.role + ': ' + f.error).join(', '));
1354
+ } else {
1355
+ const sub = [
1356
+ result.phase2Pending ? `${result.phase2Pending} QC queued` : '',
1357
+ result.projectDir ? `→ ${result.projectDir}/` : '',
1358
+ ].filter(Boolean).join(' · ');
1359
+ get().addToast('success', `Launched ${totalOk} agents`, sub || undefined);
1360
+ if (failures.length > 0) {
1361
+ get().addToast('warning', `${failures.length} agent(s) failed to spawn`, failures.map(f => f.role + ': ' + f.error).join(', '));
1362
+ }
1363
+ }
1333
1364
  // Set thinking indicator for all launched/reused agents
1334
1365
  const launchedAgents = result.agents || [];
1335
1366
  if (launchedAgents.length > 0) {
@@ -1718,6 +1749,15 @@ export const useGrooveStore = create((set, get) => ({
1718
1749
  }
1719
1750
  },
1720
1751
 
1752
+ async setConversationModel(id, provider, model) {
1753
+ try {
1754
+ const conv = await api.patch(`/conversations/${encodeURIComponent(id)}`, { provider, model });
1755
+ set((s) => ({ conversations: s.conversations.map((c) => c.id === id ? { ...c, ...conv } : c) }));
1756
+ } catch (err) {
1757
+ get().addToast('error', 'Model change failed', err.message);
1758
+ }
1759
+ },
1760
+
1721
1761
  async stopChatStreaming(conversationId) {
1722
1762
  try {
1723
1763
  await api.post(`/conversations/${encodeURIComponent(conversationId)}/stop`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groove-dev",
3
- "version": "0.27.60",
3
+ "version": "0.27.62",
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.60",
3
+ "version": "0.27.62",
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.60",
3
+ "version": "0.27.62",
4
4
  "description": "GROOVE daemon — agent orchestration engine",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",