groove-dev 0.27.148 → 0.27.150

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,7 +6,7 @@
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-BKbsE_hn.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-CReKPWhY.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">
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/gui",
3
- "version": "0.27.148",
3
+ "version": "0.27.150",
4
4
  "description": "GROOVE GUI — visual agent control plane",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -91,6 +91,7 @@ function AddRuntimeDialog({ open, onOpenChange }) {
91
91
 
92
92
  function RuntimeItem({ runtime, active, onSelect, onTest, onRemove, onStop, onStart, testing }) {
93
93
  const online = runtime.status === 'connected';
94
+ const starting = runtime.status === 'starting';
94
95
  const managed = !!(runtime._localModelId || runtime._mlxModelId || runtime.launchConfig || runtime.type === 'mlx' || runtime.type === 'llama-cpp');
95
96
  return (
96
97
  <button
@@ -102,15 +103,15 @@ function RuntimeItem({ runtime, active, onSelect, onTest, onRemove, onStop, onSt
102
103
  >
103
104
  <span className={cn(
104
105
  'w-1.5 h-1.5 rounded-full flex-shrink-0',
105
- online ? 'bg-success' : runtime.status === 'error' ? 'bg-danger' : 'bg-text-4',
106
+ starting ? 'bg-warning animate-pulse' : online ? 'bg-success' : runtime.status === 'error' ? 'bg-danger' : 'bg-text-4',
106
107
  )} />
107
108
  <div className="flex-1 min-w-0">
108
109
  <div className={cn('text-[11px] font-sans font-medium truncate', active ? 'text-text-0' : 'text-text-2')}>
109
110
  {RUNTIME_TYPES.find((t) => t.value === runtime.type)?.label || runtime.type}
110
111
  </div>
111
112
  <div className="text-[10px] text-text-4 flex items-center gap-1.5">
112
- <span className={cn('font-sans', online ? 'text-success' : 'text-danger')}>
113
- {online ? 'Online' : 'Offline'}
113
+ <span className={cn('font-sans', starting ? 'text-warning' : online ? 'text-success' : 'text-danger')}>
114
+ {starting ? 'Starting...' : online ? 'Online' : 'Offline'}
114
115
  </span>
115
116
  {runtime.latency != null && online && (
116
117
  <span className="font-mono">{Math.round(runtime.latency)}ms</span>
@@ -128,7 +129,14 @@ function RuntimeItem({ runtime, active, onSelect, onTest, onRemove, onStop, onSt
128
129
  </button>
129
130
  </Tooltip>
130
131
  )}
131
- {managed && !online && (
132
+ {starting && (
133
+ <Tooltip content="Starting server...">
134
+ <span className="p-1 text-warning">
135
+ <Loader2 size={10} className="animate-spin" />
136
+ </span>
137
+ </Tooltip>
138
+ )}
139
+ {managed && !online && !starting && (
132
140
  <Tooltip content="Start server">
133
141
  <button
134
142
  onClick={(e) => { e.stopPropagation(); onStart(runtime.id); }}
@@ -26,7 +26,7 @@ export function StatusBar({
26
26
  const labRuntimes = useGrooveStore((s) => s.labRuntimes);
27
27
  const stopLabRuntime = useGrooveStore((s) => s.stopLabRuntime);
28
28
  const activeTunnels = savedTunnels.filter((t) => t.active);
29
- const runningRuntimes = (labRuntimes || []).filter((rt) => rt.status === 'connected');
29
+ const visibleRuntimes = (labRuntimes || []).filter((rt) => rt.status === 'connected' || rt.status === 'starting');
30
30
  const electron = isElectron();
31
31
 
32
32
  return (
@@ -113,26 +113,35 @@ export function StatusBar({
113
113
  <span>Federation</span>
114
114
  </button>
115
115
  )}
116
- {runningRuntimes.map((rt) => (
117
- <div key={rt.id} className="flex items-center gap-1">
118
- <button
119
- onClick={() => navigate('model-lab')}
120
- className="flex items-center gap-1.5 text-text-3 hover:text-text-1 cursor-pointer transition-colors"
121
- title={`${rt.name} running`}
122
- >
123
- <Cpu size={10} className="text-success" />
124
- <span>{rt.name}</span>
125
- <span className="w-1.5 h-1.5 rounded-full bg-success" />
126
- </button>
127
- <button
128
- onClick={() => stopLabRuntime(rt.id)}
129
- className="p-0.5 text-text-4 hover:text-danger cursor-pointer transition-colors rounded"
130
- title={`Stop ${rt.name}`}
131
- >
132
- <Square size={8} />
133
- </button>
134
- </div>
135
- ))}
116
+ {visibleRuntimes.map((rt) => {
117
+ const starting = rt.status === 'starting';
118
+ return (
119
+ <div key={rt.id} className="flex items-center gap-1">
120
+ <button
121
+ onClick={() => navigate('model-lab')}
122
+ className="flex items-center gap-1.5 text-text-3 hover:text-text-1 cursor-pointer transition-colors"
123
+ title={`${rt.name} ${starting ? 'starting...' : 'running'}`}
124
+ >
125
+ <Cpu size={10} className={starting ? 'text-warning animate-pulse' : 'text-success'} />
126
+ <span>{rt.name}</span>
127
+ {starting ? (
128
+ <span className="w-1.5 h-1.5 rounded-full bg-warning animate-pulse" />
129
+ ) : (
130
+ <span className="w-1.5 h-1.5 rounded-full bg-success" />
131
+ )}
132
+ </button>
133
+ {!starting && (
134
+ <button
135
+ onClick={() => stopLabRuntime(rt.id)}
136
+ className="p-0.5 text-text-4 hover:text-danger cursor-pointer transition-colors rounded"
137
+ title={`Stop ${rt.name}`}
138
+ >
139
+ <Square size={8} />
140
+ </button>
141
+ )}
142
+ </div>
143
+ );
144
+ })}
136
145
  </div>
137
146
 
138
147
  <div className="flex-1" />
@@ -396,12 +396,17 @@ export const createProvidersSlice = (set, get) => ({
396
396
 
397
397
  async startLabRuntime(id) {
398
398
  try {
399
- get().addToast('info', 'Starting server...');
399
+ const runtimes = get().labRuntimes.map((r) =>
400
+ r.id === id ? { ...r, status: 'starting' } : r,
401
+ );
402
+ set({ labRuntimes: runtimes });
403
+ persistJSON('groove:labRuntimes', runtimes);
400
404
  await api.post(`/lab/runtimes/${id}/start`);
401
405
  await get().fetchLabRuntimes();
402
406
  get().setLabActiveRuntime(id);
403
407
  get().addToast('success', 'Server started');
404
408
  } catch (err) {
409
+ await get().fetchLabRuntimes();
405
410
  get().addToast('error', 'Failed to start server', err.message);
406
411
  }
407
412
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groove-dev",
3
- "version": "0.27.148",
3
+ "version": "0.27.150",
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.148",
3
+ "version": "0.27.150",
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.148",
3
+ "version": "0.27.150",
4
4
  "description": "GROOVE daemon — agent orchestration engine",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -778,7 +778,9 @@ export class ProcessManager {
778
778
  return true;
779
779
  };
780
780
 
781
- if (!isProviderAuthed(providerName)) {
781
+ // Skip auth fallback for local provider when model references a lab runtime or GGUF
782
+ const hasLabModel = config.model && (config.model.startsWith('runtime:') || config.model.startsWith('gguf:'));
783
+ if (!isProviderAuthed(providerName) && !(providerName === 'local' && hasLabModel)) {
782
784
  const priority = ['claude-code', 'gemini', 'codex', 'local', 'ollama'];
783
785
  const fallback = priority.find(p => p !== providerName && isProviderAuthed(p));
784
786
  if (fallback) {
@@ -790,7 +792,7 @@ export class ProcessManager {
790
792
  if (!provider) {
791
793
  throw new Error(`Unknown provider: ${providerName}`);
792
794
  }
793
- if (!provider.constructor.isInstalled()) {
795
+ if (!provider.constructor.isInstalled() && !(providerName === 'local' && hasLabModel)) {
794
796
  const installed = getInstalledProviders();
795
797
  if (installed.length > 0) {
796
798
  throw new Error(
@@ -859,7 +861,9 @@ export class ProcessManager {
859
861
  }
860
862
 
861
863
  // Validate explicit model against provider's supported models
862
- if (config.model && config.model !== 'auto' && provider.constructor.models) {
864
+ // Skip validation for runtime: and gguf: models — they're resolved by getLoopConfig()
865
+ const skipModelValidation = config.model && (config.model.startsWith('runtime:') || config.model.startsWith('gguf:'));
866
+ if (config.model && config.model !== 'auto' && provider.constructor.models && !skipModelValidation) {
863
867
  const valid = provider.constructor.models.some(m => m.id === config.model);
864
868
  if (!valid) {
865
869
  const fallback = provider.constructor.models[0];