groove-dev 0.27.140 → 0.27.142
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.
- package/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/integrations-registry.json +12 -44
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/api.js +100 -23
- package/node_modules/@groove-dev/daemon/src/integrations.js +10 -0
- package/node_modules/@groove-dev/daemon/src/introducer.js +1 -1
- package/node_modules/@groove-dev/daemon/src/journalist.js +171 -1
- package/node_modules/@groove-dev/daemon/src/keeper.js +2 -2
- package/node_modules/@groove-dev/daemon/src/memory.js +8 -5
- package/node_modules/@groove-dev/daemon/src/model-lab.js +11 -0
- package/node_modules/@groove-dev/daemon/src/process.js +65 -0
- package/node_modules/@groove-dev/daemon/src/rotator.js +25 -8
- package/node_modules/@groove-dev/daemon/src/validate.js +8 -0
- package/node_modules/@groove-dev/gui/dist/assets/{codemirror-BQqYnZfL.js → codemirror-BYKpdS2W.js} +10 -10
- package/node_modules/@groove-dev/gui/dist/assets/index-Bjd91ufV.js +984 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-BqdwIFn4.css +1 -0
- package/node_modules/@groove-dev/gui/dist/index.html +3 -3
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/app.jsx +0 -2
- package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +3 -4
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +8 -2
- package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +12 -8
- package/node_modules/@groove-dev/gui/src/components/agents/agent-panel.jsx +79 -5
- package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +5 -4
- package/node_modules/@groove-dev/gui/src/components/agents/workspace-mode.jsx +109 -12
- package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +111 -0
- package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +70 -33
- package/node_modules/@groove-dev/gui/src/components/editor/ai-panel.jsx +77 -6
- package/node_modules/@groove-dev/gui/src/components/editor/code-editor.jsx +2 -68
- package/node_modules/@groove-dev/gui/src/components/editor/file-tree.jsx +2 -49
- package/node_modules/@groove-dev/gui/src/components/editor/terminal.jsx +15 -4
- package/node_modules/@groove-dev/gui/src/components/keeper/global-modals.jsx +10 -10
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +1 -2
- package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +151 -3
- package/node_modules/@groove-dev/gui/src/components/marketplace/integration-wizard.jsx +223 -18
- package/node_modules/@groove-dev/gui/src/stores/groove.js +107 -29
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +114 -56
- package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +2 -0
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +3 -71
- package/node_modules/@groove-dev/gui/src/views/memory.jsx +9 -9
- package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +1 -6
- package/node_modules/@groove-dev/gui/src/views/models.jsx +658 -565
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/integrations-registry.json +12 -44
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/api.js +100 -23
- package/packages/daemon/src/integrations.js +10 -0
- package/packages/daemon/src/introducer.js +1 -1
- package/packages/daemon/src/journalist.js +171 -1
- package/packages/daemon/src/keeper.js +2 -2
- package/packages/daemon/src/memory.js +8 -5
- package/packages/daemon/src/model-lab.js +11 -0
- package/packages/daemon/src/process.js +65 -0
- package/packages/daemon/src/rotator.js +25 -8
- package/packages/daemon/src/validate.js +8 -0
- package/packages/gui/dist/assets/{codemirror-BQqYnZfL.js → codemirror-BYKpdS2W.js} +10 -10
- package/packages/gui/dist/assets/index-Bjd91ufV.js +984 -0
- package/packages/gui/dist/assets/index-BqdwIFn4.css +1 -0
- package/packages/gui/dist/index.html +3 -3
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/app.jsx +0 -2
- package/packages/gui/src/components/agents/agent-chat.jsx +3 -4
- package/packages/gui/src/components/agents/agent-feed.jsx +8 -2
- package/packages/gui/src/components/agents/agent-file-tree.jsx +12 -8
- package/packages/gui/src/components/agents/agent-panel.jsx +79 -5
- package/packages/gui/src/components/agents/code-review.jsx +5 -4
- package/packages/gui/src/components/agents/workspace-mode.jsx +109 -12
- package/packages/gui/src/components/dashboard/context-gauges.jsx +111 -0
- package/packages/gui/src/components/dashboard/routing-chart.jsx +70 -33
- package/packages/gui/src/components/editor/ai-panel.jsx +77 -6
- package/packages/gui/src/components/editor/code-editor.jsx +2 -68
- package/packages/gui/src/components/editor/file-tree.jsx +2 -49
- package/packages/gui/src/components/editor/terminal.jsx +15 -4
- package/packages/gui/src/components/keeper/global-modals.jsx +10 -10
- package/packages/gui/src/components/layout/activity-bar.jsx +1 -2
- package/packages/gui/src/components/layout/terminal-panel.jsx +151 -3
- package/packages/gui/src/components/marketplace/integration-wizard.jsx +223 -18
- package/packages/gui/src/stores/groove.js +107 -29
- package/packages/gui/src/views/agents.jsx +114 -56
- package/packages/gui/src/views/dashboard.jsx +2 -0
- package/packages/gui/src/views/marketplace.jsx +3 -71
- package/packages/gui/src/views/memory.jsx +9 -9
- package/packages/gui/src/views/model-lab.jsx +1 -6
- package/packages/gui/src/views/models.jsx +658 -565
- package/plan_files/keeper-manual.md +53 -42
- package/node_modules/@groove-dev/gui/dist/assets/index-BV9CAiw1.css +0 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-DK6UIz0n.js +0 -8698
- package/node_modules/@groove-dev/gui/src/components/toys/toy-card.jsx +0 -78
- package/node_modules/@groove-dev/gui/src/components/toys/toy-creator.jsx +0 -144
- package/node_modules/@groove-dev/gui/src/components/toys/toy-launcher.jsx +0 -187
- package/node_modules/@groove-dev/gui/src/views/toys.jsx +0 -162
- package/packages/gui/dist/assets/index-BV9CAiw1.css +0 -1
- package/packages/gui/dist/assets/index-DK6UIz0n.js +0 -8698
- package/packages/gui/src/components/toys/toy-card.jsx +0 -78
- package/packages/gui/src/components/toys/toy-creator.jsx +0 -144
- package/packages/gui/src/components/toys/toy-launcher.jsx +0 -187
- package/packages/gui/src/views/toys.jsx +0 -162
|
@@ -4,31 +4,43 @@ import { Dialog, DialogContent } from '../ui/dialog';
|
|
|
4
4
|
import { Button } from '../ui/button';
|
|
5
5
|
import { Input } from '../ui/input';
|
|
6
6
|
import { Badge } from '../ui/badge';
|
|
7
|
+
import { ScrollArea } from '../ui/scroll-area';
|
|
7
8
|
import { api } from '../../lib/api';
|
|
8
9
|
import { useToast } from '../../lib/hooks/use-toast';
|
|
10
|
+
import { useGrooveStore } from '../../stores/groove';
|
|
9
11
|
import { integrationOAuth } from '../../lib/electron';
|
|
10
12
|
import {
|
|
11
13
|
Check, CheckCircle, ExternalLink, Loader2, Eye, EyeOff,
|
|
12
|
-
Key, Shield, Trash2, ChevronRight,
|
|
14
|
+
Key, Shield, Trash2, ChevronRight, Copy, RefreshCw,
|
|
15
|
+
Users, Rocket, Bot,
|
|
13
16
|
} from 'lucide-react';
|
|
14
17
|
|
|
15
18
|
import { INTEGRATION_LOGOS } from '../../lib/integration-logos';
|
|
16
19
|
|
|
20
|
+
const ICON_PALETTES = [
|
|
21
|
+
'bg-accent/15 text-accent',
|
|
22
|
+
'bg-purple/15 text-purple',
|
|
23
|
+
'bg-success/15 text-success',
|
|
24
|
+
'bg-warning/15 text-warning',
|
|
25
|
+
'bg-danger/15 text-danger',
|
|
26
|
+
'bg-info/15 text-info',
|
|
27
|
+
];
|
|
28
|
+
|
|
17
29
|
function IntegrationIcon({ item, size = 48 }) {
|
|
18
30
|
const logoUrl = INTEGRATION_LOGOS[item.id];
|
|
19
31
|
if (logoUrl) {
|
|
20
32
|
return (
|
|
21
33
|
<div className="rounded-lg bg-surface-4 flex items-center justify-center flex-shrink-0 overflow-hidden" style={{ width: size, height: size }}>
|
|
22
|
-
<img src={logoUrl} alt={item.name} className="w-6 h-6" onError={(e) => { e.target.
|
|
34
|
+
<img src={logoUrl} alt={item.name} className="w-6 h-6" onError={(e) => { e.target.classList.add('hidden'); }} />
|
|
23
35
|
</div>
|
|
24
36
|
);
|
|
25
37
|
}
|
|
26
38
|
const initial = (item.name || '?')[0].toUpperCase();
|
|
27
|
-
const
|
|
39
|
+
const palette = ICON_PALETTES[(item.name || '').charCodeAt(0) % ICON_PALETTES.length];
|
|
28
40
|
return (
|
|
29
41
|
<div
|
|
30
|
-
className=
|
|
31
|
-
style={{ width: size, height: size
|
|
42
|
+
className={`rounded-lg flex items-center justify-center flex-shrink-0 text-xl font-bold font-sans ${palette}`}
|
|
43
|
+
style={{ width: size, height: size }}
|
|
32
44
|
>
|
|
33
45
|
{initial}
|
|
34
46
|
</div>
|
|
@@ -219,6 +231,199 @@ function OverviewStep({ item, status, installing, onInstall, onUninstall, onNext
|
|
|
219
231
|
);
|
|
220
232
|
}
|
|
221
233
|
|
|
234
|
+
// ── Step: Agent Setup ──────────────────────────────────
|
|
235
|
+
function AgentSetupStep({ item, onClose }) {
|
|
236
|
+
const agents = useGrooveStore((s) => s.agents);
|
|
237
|
+
const teams = useGrooveStore((s) => s.teams);
|
|
238
|
+
const installViaExistingAgent = useGrooveStore((s) => s.installViaExistingAgent);
|
|
239
|
+
const spawnIntegrationTeam = useGrooveStore((s) => s.spawnIntegrationTeam);
|
|
240
|
+
const [mode, setMode] = useState(null); // null | 'existing' | 'spawn'
|
|
241
|
+
const [spawning, setSpawning] = useState(false);
|
|
242
|
+
const [selectedAgentId, setSelectedAgentId] = useState(null);
|
|
243
|
+
|
|
244
|
+
const runningAgents = agents.filter((a) => a.status === 'running' || a.status === 'idle');
|
|
245
|
+
|
|
246
|
+
const agentsByTeam = {};
|
|
247
|
+
for (const agent of runningAgents) {
|
|
248
|
+
const teamId = agent.teamId || '_none';
|
|
249
|
+
if (!agentsByTeam[teamId]) agentsByTeam[teamId] = [];
|
|
250
|
+
agentsByTeam[teamId].push(agent);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const teamMap = {};
|
|
254
|
+
for (const t of teams) teamMap[t.id] = t.name;
|
|
255
|
+
|
|
256
|
+
async function handleExistingAgent() {
|
|
257
|
+
if (!selectedAgentId) return;
|
|
258
|
+
await installViaExistingAgent(item, selectedAgentId);
|
|
259
|
+
onClose();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function handleSpawnNew() {
|
|
263
|
+
setSpawning(true);
|
|
264
|
+
try {
|
|
265
|
+
await spawnIntegrationTeam(item);
|
|
266
|
+
onClose();
|
|
267
|
+
} catch {
|
|
268
|
+
setSpawning(false);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Option picker when no mode selected
|
|
273
|
+
if (!mode) {
|
|
274
|
+
return (
|
|
275
|
+
<div className="px-5 py-5 space-y-5">
|
|
276
|
+
<div className="flex items-start gap-4">
|
|
277
|
+
<IntegrationIcon item={item} size={52} />
|
|
278
|
+
<div className="flex-1 min-w-0">
|
|
279
|
+
<h2 className="text-base font-bold text-text-0 font-sans">Install {item.name}</h2>
|
|
280
|
+
<p className="text-xs text-text-3 font-sans mt-0.5">
|
|
281
|
+
Choose how to set up this integration
|
|
282
|
+
</p>
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
|
|
286
|
+
<div className="h-px bg-border-subtle" />
|
|
287
|
+
|
|
288
|
+
<div className="space-y-2.5">
|
|
289
|
+
<button
|
|
290
|
+
onClick={() => runningAgents.length > 0 ? setMode('existing') : null}
|
|
291
|
+
disabled={runningAgents.length === 0}
|
|
292
|
+
className="w-full text-left px-4 py-3.5 rounded-lg border border-border-subtle bg-surface-2 hover:bg-surface-3 hover:border-accent/30 transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed group"
|
|
293
|
+
>
|
|
294
|
+
<div className="flex items-center gap-3">
|
|
295
|
+
<div className="w-9 h-9 rounded-lg bg-accent/10 flex items-center justify-center flex-shrink-0 group-hover:bg-accent/15 transition-colors">
|
|
296
|
+
<Users size={18} className="text-accent" />
|
|
297
|
+
</div>
|
|
298
|
+
<div className="flex-1 min-w-0">
|
|
299
|
+
<div className="text-sm font-semibold text-text-0 font-sans">Use Existing Agent</div>
|
|
300
|
+
<div className="text-2xs text-text-3 font-sans mt-0.5">
|
|
301
|
+
{runningAgents.length > 0
|
|
302
|
+
? `Send setup instructions to one of ${runningAgents.length} running agent${runningAgents.length !== 1 ? 's' : ''}`
|
|
303
|
+
: 'No agents running — spawn one first'}
|
|
304
|
+
</div>
|
|
305
|
+
</div>
|
|
306
|
+
{runningAgents.length > 0 && <ChevronRight size={14} className="text-text-4 group-hover:text-accent transition-colors" />}
|
|
307
|
+
</div>
|
|
308
|
+
</button>
|
|
309
|
+
|
|
310
|
+
<button
|
|
311
|
+
onClick={() => setMode('spawn')}
|
|
312
|
+
className="w-full text-left px-4 py-3.5 rounded-lg border border-border-subtle bg-surface-2 hover:bg-surface-3 hover:border-accent/30 transition-all cursor-pointer group"
|
|
313
|
+
>
|
|
314
|
+
<div className="flex items-center gap-3">
|
|
315
|
+
<div className="w-9 h-9 rounded-lg bg-purple/10 flex items-center justify-center flex-shrink-0 group-hover:bg-purple/15 transition-colors">
|
|
316
|
+
<Rocket size={18} className="text-purple" />
|
|
317
|
+
</div>
|
|
318
|
+
<div className="flex-1 min-w-0">
|
|
319
|
+
<div className="text-sm font-semibold text-text-0 font-sans">Spawn New Agent</div>
|
|
320
|
+
<div className="text-2xs text-text-3 font-sans mt-0.5">
|
|
321
|
+
Create a dedicated team and planner for this integration
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
<ChevronRight size={14} className="text-text-4 group-hover:text-accent transition-colors" />
|
|
325
|
+
</div>
|
|
326
|
+
</button>
|
|
327
|
+
</div>
|
|
328
|
+
</div>
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Spawn new agent confirmation
|
|
333
|
+
if (mode === 'spawn') {
|
|
334
|
+
return (
|
|
335
|
+
<div className="px-5 py-5 space-y-5">
|
|
336
|
+
<div className="flex items-center gap-3">
|
|
337
|
+
<IntegrationIcon item={item} size={36} />
|
|
338
|
+
<div>
|
|
339
|
+
<h2 className="text-sm font-bold text-text-0 font-sans">Spawn Integration Agent</h2>
|
|
340
|
+
<p className="text-2xs text-text-3 font-sans">Creates a team and planner for {item.name}</p>
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
|
|
344
|
+
<div className="bg-surface-2 rounded-md px-4 py-3 space-y-2">
|
|
345
|
+
<span className="text-xs font-semibold text-text-1 font-sans">What happens next</span>
|
|
346
|
+
<ol className="space-y-1.5">
|
|
347
|
+
{[
|
|
348
|
+
`A new team "${item.name}" will be created`,
|
|
349
|
+
'A planner agent will spawn with full integration context',
|
|
350
|
+
'The agent will handle installation and configuration',
|
|
351
|
+
].map((step, i) => (
|
|
352
|
+
<li key={i} className="flex gap-2 text-xs text-text-2 font-sans leading-relaxed">
|
|
353
|
+
<span className="text-accent font-mono flex-shrink-0 w-4 text-right">{i + 1}.</span>
|
|
354
|
+
<span>{step}</span>
|
|
355
|
+
</li>
|
|
356
|
+
))}
|
|
357
|
+
</ol>
|
|
358
|
+
</div>
|
|
359
|
+
|
|
360
|
+
<div className="flex gap-2">
|
|
361
|
+
<Button variant="secondary" size="lg" onClick={() => setMode(null)} className="flex-1" disabled={spawning}>
|
|
362
|
+
Back
|
|
363
|
+
</Button>
|
|
364
|
+
<Button variant="primary" size="lg" onClick={handleSpawnNew} disabled={spawning} className="flex-1 gap-2">
|
|
365
|
+
{spawning ? <><Loader2 size={14} className="animate-spin" /> Spawning...</> : <><Rocket size={14} /> Spawn Agent</>}
|
|
366
|
+
</Button>
|
|
367
|
+
</div>
|
|
368
|
+
</div>
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Pick existing agent
|
|
373
|
+
return (
|
|
374
|
+
<div className="px-5 py-5 space-y-4">
|
|
375
|
+
<div className="flex items-center gap-3">
|
|
376
|
+
<IntegrationIcon item={item} size={36} />
|
|
377
|
+
<div>
|
|
378
|
+
<h2 className="text-sm font-bold text-text-0 font-sans">Choose an Agent</h2>
|
|
379
|
+
<p className="text-2xs text-text-3 font-sans">Send {item.name} setup instructions to a running agent</p>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
|
|
383
|
+
<ScrollArea className="max-h-64">
|
|
384
|
+
<div className="space-y-3">
|
|
385
|
+
{Object.entries(agentsByTeam).map(([teamId, teamAgents]) => (
|
|
386
|
+
<div key={teamId}>
|
|
387
|
+
<div className="text-2xs font-semibold text-text-3 font-sans uppercase tracking-wider mb-1.5 px-1">
|
|
388
|
+
{teamMap[teamId] || 'Unassigned'}
|
|
389
|
+
</div>
|
|
390
|
+
<div className="space-y-1">
|
|
391
|
+
{teamAgents.map((agent) => (
|
|
392
|
+
<button
|
|
393
|
+
key={agent.id}
|
|
394
|
+
onClick={() => setSelectedAgentId(agent.id)}
|
|
395
|
+
className={`w-full text-left px-3 py-2.5 rounded-md border transition-all cursor-pointer flex items-center gap-3 ${
|
|
396
|
+
selectedAgentId === agent.id
|
|
397
|
+
? 'border-accent bg-accent/8'
|
|
398
|
+
: 'border-border-subtle bg-surface-2 hover:bg-surface-3 hover:border-border'
|
|
399
|
+
}`}
|
|
400
|
+
>
|
|
401
|
+
<Bot size={14} className={selectedAgentId === agent.id ? 'text-accent' : 'text-text-4'} />
|
|
402
|
+
<div className="flex-1 min-w-0">
|
|
403
|
+
<div className="text-xs font-semibold text-text-0 font-sans truncate">{agent.name || agent.id}</div>
|
|
404
|
+
<div className="text-2xs text-text-3 font-sans">{agent.role}</div>
|
|
405
|
+
</div>
|
|
406
|
+
{selectedAgentId === agent.id && <Check size={14} className="text-accent flex-shrink-0" />}
|
|
407
|
+
</button>
|
|
408
|
+
))}
|
|
409
|
+
</div>
|
|
410
|
+
</div>
|
|
411
|
+
))}
|
|
412
|
+
</div>
|
|
413
|
+
</ScrollArea>
|
|
414
|
+
|
|
415
|
+
<div className="flex gap-2">
|
|
416
|
+
<Button variant="secondary" size="lg" onClick={() => { setMode(null); setSelectedAgentId(null); }} className="flex-1">
|
|
417
|
+
Back
|
|
418
|
+
</Button>
|
|
419
|
+
<Button variant="primary" size="lg" onClick={handleExistingAgent} disabled={!selectedAgentId} className="flex-1 gap-2">
|
|
420
|
+
<Bot size={14} /> Send Instructions
|
|
421
|
+
</Button>
|
|
422
|
+
</div>
|
|
423
|
+
</div>
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
222
427
|
// ── Which APIs each Google integration needs ───────────
|
|
223
428
|
const GOOGLE_API_NAMES = {
|
|
224
429
|
gmail: 'Gmail API',
|
|
@@ -569,7 +774,7 @@ function DoneStep({ item, onClose }) {
|
|
|
569
774
|
// ── Main Wizard ─────────────────────────────────────────
|
|
570
775
|
export function IntegrationWizard({ integration, open, onClose }) {
|
|
571
776
|
const toast = useToast();
|
|
572
|
-
const [step, setStep] = useState('overview'); // overview | configure | done
|
|
777
|
+
const [step, setStep] = useState('overview'); // overview | agent-setup | configure | done
|
|
573
778
|
const [status, setStatus] = useState(null);
|
|
574
779
|
const [installing, setInstalling] = useState(false);
|
|
575
780
|
const [loadingStatus, setLoadingStatus] = useState(true);
|
|
@@ -595,17 +800,8 @@ export function IntegrationWizard({ integration, open, onClose }) {
|
|
|
595
800
|
}
|
|
596
801
|
}, [open, integration, fetchStatus]);
|
|
597
802
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
try {
|
|
601
|
-
await api.post(`/integrations/${integration.id}/install`);
|
|
602
|
-
toast.success(`${integration.name} installed`);
|
|
603
|
-
await fetchStatus();
|
|
604
|
-
setStep('configure');
|
|
605
|
-
} catch (err) {
|
|
606
|
-
toast.error('Install failed', err.message);
|
|
607
|
-
}
|
|
608
|
-
setInstalling(false);
|
|
803
|
+
function handleInstall() {
|
|
804
|
+
setStep('agent-setup');
|
|
609
805
|
}
|
|
610
806
|
|
|
611
807
|
async function handleUninstall() {
|
|
@@ -628,10 +824,17 @@ export function IntegrationWizard({ integration, open, onClose }) {
|
|
|
628
824
|
|
|
629
825
|
if (!integration) return null;
|
|
630
826
|
|
|
827
|
+
const stepTitle = {
|
|
828
|
+
overview: integration.name,
|
|
829
|
+
'agent-setup': 'Install',
|
|
830
|
+
configure: 'Configure',
|
|
831
|
+
done: 'Complete',
|
|
832
|
+
};
|
|
833
|
+
|
|
631
834
|
return (
|
|
632
835
|
<Dialog open={open} onOpenChange={(v) => { if (!v) onClose(); }}>
|
|
633
836
|
<DialogContent
|
|
634
|
-
title={step
|
|
837
|
+
title={stepTitle[step] || integration.name}
|
|
635
838
|
description={`Setup wizard for ${integration.name}`}
|
|
636
839
|
className="max-w-md"
|
|
637
840
|
>
|
|
@@ -648,6 +851,8 @@ export function IntegrationWizard({ integration, open, onClose }) {
|
|
|
648
851
|
onUninstall={handleUninstall}
|
|
649
852
|
onNext={handleConfigureNext}
|
|
650
853
|
/>
|
|
854
|
+
) : step === 'agent-setup' ? (
|
|
855
|
+
<AgentSetupStep item={integration} onClose={onClose} />
|
|
651
856
|
) : step === 'configure' ? (
|
|
652
857
|
<ConfigureStep
|
|
653
858
|
item={integration}
|
|
@@ -213,6 +213,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
213
213
|
|
|
214
214
|
// ── Editor (Cursor-style) ────────────────────────────────
|
|
215
215
|
editorSelectedAgent: null,
|
|
216
|
+
editorPendingSnippet: null,
|
|
216
217
|
editorViewMode: 'code',
|
|
217
218
|
editorAiPanelOpen: false,
|
|
218
219
|
editorAiPanelWidth: Number(localStorage.getItem('groove:editorAiPanelWidth')) || 360,
|
|
@@ -665,6 +666,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
665
666
|
case 'lab:runtime:added':
|
|
666
667
|
case 'lab:runtime:updated':
|
|
667
668
|
case 'lab:runtime:removed':
|
|
669
|
+
case 'llama:server:stopped':
|
|
668
670
|
get().fetchLabRuntimes();
|
|
669
671
|
break;
|
|
670
672
|
|
|
@@ -1335,23 +1337,21 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
1335
1337
|
}
|
|
1336
1338
|
|
|
1337
1339
|
case 'save': {
|
|
1338
|
-
if (tags.length === 0) { addSystemMsg('Usage:
|
|
1340
|
+
if (tags.length === 0) { addSystemMsg('Usage: save #tag your message here'); return true; }
|
|
1339
1341
|
const content = rest.replace(/#[\w/.-]+/g, '').trim();
|
|
1340
|
-
if (!content) { addSystemMsg('Usage:
|
|
1341
|
-
get().addChatMessage(agentId, 'user', message, false);
|
|
1342
|
+
if (!content) { addSystemMsg('Usage: save #tag your message here'); return true; }
|
|
1342
1343
|
await get().saveKeeperItem(tags[0], content);
|
|
1343
1344
|
addSystemMsg(`Saved to #${tags[0]}`);
|
|
1344
|
-
return
|
|
1345
|
+
return { passthrough: content };
|
|
1345
1346
|
}
|
|
1346
1347
|
|
|
1347
1348
|
case 'append': {
|
|
1348
|
-
if (tags.length === 0) { addSystemMsg('Usage:
|
|
1349
|
+
if (tags.length === 0) { addSystemMsg('Usage: append #tag content to add'); return true; }
|
|
1349
1350
|
const content = rest.replace(/#[\w/.-]+/g, '').trim();
|
|
1350
|
-
if (!content) { addSystemMsg('Usage:
|
|
1351
|
-
get().addChatMessage(agentId, 'user', message, false);
|
|
1351
|
+
if (!content) { addSystemMsg('Usage: append #tag content to add'); return true; }
|
|
1352
1352
|
await get().appendKeeperItem(tags[0], content);
|
|
1353
1353
|
addSystemMsg(`Appended to #${tags[0]}`);
|
|
1354
|
-
return
|
|
1354
|
+
return { passthrough: content };
|
|
1355
1355
|
}
|
|
1356
1356
|
|
|
1357
1357
|
case 'update': {
|
|
@@ -2031,7 +2031,6 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
2031
2031
|
...(tlc?.reasoningEffort != null && { teamReasoningEffort: tlc.reasoningEffort }),
|
|
2032
2032
|
...(tlc?.temperature != null && { teamTemperature: tlc.temperature }),
|
|
2033
2033
|
...(tlc?.verbosity != null && { teamVerbosity: tlc.verbosity }),
|
|
2034
|
-
...(tlc?.mode && { mode: tlc.mode }),
|
|
2035
2034
|
};
|
|
2036
2035
|
const result = await api.post('/recommended-team/launch', body);
|
|
2037
2036
|
const totalOk = (result.launched || 0) + (result.reused || 0);
|
|
@@ -2688,7 +2687,10 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
2688
2687
|
const keeperCmd = message.match(/\[(save|append|update|delete|view|doc|link|read|instruct)\]/i);
|
|
2689
2688
|
if (keeperCmd) {
|
|
2690
2689
|
const handled = await get()._handleKeeperCommand(id, message, keeperCmd[1].toLowerCase());
|
|
2691
|
-
if (handled) return { status: 'keeper_handled' };
|
|
2690
|
+
if (handled === true) return { status: 'keeper_handled' };
|
|
2691
|
+
if (handled?.passthrough) {
|
|
2692
|
+
message = handled.passthrough;
|
|
2693
|
+
}
|
|
2692
2694
|
}
|
|
2693
2695
|
|
|
2694
2696
|
get().addChatMessage(id, 'user', message, false);
|
|
@@ -3226,10 +3228,27 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
3226
3228
|
set({ editorQuickSearchOpen: open });
|
|
3227
3229
|
},
|
|
3228
3230
|
|
|
3231
|
+
attachSnippet(snippet) {
|
|
3232
|
+
set({ editorPendingSnippet: snippet });
|
|
3233
|
+
if (!get().editorAiPanelOpen) {
|
|
3234
|
+
set({ editorAiPanelOpen: true });
|
|
3235
|
+
}
|
|
3236
|
+
},
|
|
3237
|
+
|
|
3238
|
+
clearSnippet() {
|
|
3239
|
+
set({ editorPendingSnippet: null });
|
|
3240
|
+
},
|
|
3241
|
+
|
|
3229
3242
|
async sendCodeToAgent(agentId, instruction, filePath, lineStart, lineEnd, selectedCode) {
|
|
3230
3243
|
if (!agentId) return;
|
|
3231
|
-
|
|
3232
|
-
|
|
3244
|
+
get().attachSnippet({
|
|
3245
|
+
type: 'code',
|
|
3246
|
+
instruction,
|
|
3247
|
+
filePath,
|
|
3248
|
+
lineStart,
|
|
3249
|
+
lineEnd,
|
|
3250
|
+
code: selectedCode,
|
|
3251
|
+
});
|
|
3233
3252
|
},
|
|
3234
3253
|
|
|
3235
3254
|
async fetchGitStatus() {
|
|
@@ -3844,11 +3863,17 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
3844
3863
|
const payload = line.slice(6);
|
|
3845
3864
|
if (payload === '[DONE]') continue;
|
|
3846
3865
|
try {
|
|
3847
|
-
const
|
|
3848
|
-
|
|
3866
|
+
const parsed = JSON.parse(payload);
|
|
3867
|
+
|
|
3868
|
+
// Support both raw OpenAI format (piped) and legacy wrapper format
|
|
3869
|
+
const delta = parsed.choices?.[0]?.delta;
|
|
3870
|
+
const reasoningText = delta?.reasoning_content || (parsed.type === 'reasoning' ? parsed.content : null);
|
|
3871
|
+
const contentText = delta?.content || (parsed.type === 'token' ? parsed.content : null);
|
|
3872
|
+
|
|
3873
|
+
if (reasoningText) {
|
|
3849
3874
|
if (!firstTokenTime) firstTokenTime = performance.now();
|
|
3850
3875
|
tokenCount++;
|
|
3851
|
-
fullReasoning +=
|
|
3876
|
+
fullReasoning += reasoningText;
|
|
3852
3877
|
set((s) => {
|
|
3853
3878
|
const sessions = s.labSessions.map((sess) => {
|
|
3854
3879
|
if (sess.id !== sessionId) return sess;
|
|
@@ -3859,10 +3884,10 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
3859
3884
|
return { labSessions: sessions };
|
|
3860
3885
|
});
|
|
3861
3886
|
}
|
|
3862
|
-
if (
|
|
3887
|
+
if (contentText) {
|
|
3863
3888
|
if (!firstTokenTime) firstTokenTime = performance.now();
|
|
3864
3889
|
tokenCount++;
|
|
3865
|
-
fullContent +=
|
|
3890
|
+
fullContent += contentText;
|
|
3866
3891
|
set((s) => {
|
|
3867
3892
|
const sessions = s.labSessions.map((sess) => {
|
|
3868
3893
|
if (sess.id !== sessionId) return sess;
|
|
@@ -3873,11 +3898,13 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
3873
3898
|
return { labSessions: sessions };
|
|
3874
3899
|
});
|
|
3875
3900
|
}
|
|
3876
|
-
|
|
3901
|
+
|
|
3902
|
+
// Handle done event (legacy wrapper) or finish_reason (raw OpenAI)
|
|
3903
|
+
if (parsed.type === 'done' && parsed.metrics) {
|
|
3877
3904
|
const elapsed = performance.now() - startTime;
|
|
3878
3905
|
const ttft = firstTokenTime ? firstTokenTime - startTime : null;
|
|
3879
3906
|
const tps = tokenCount > 0 && elapsed > 0 ? (tokenCount / (elapsed / 1000)) : null;
|
|
3880
|
-
const msgMetrics = { ttft, tokensPerSec: tps, tokens: tokenCount, generationTime: elapsed, ...
|
|
3907
|
+
const msgMetrics = { ttft, tokensPerSec: tps, tokens: tokenCount, generationTime: elapsed, ...parsed.metrics };
|
|
3881
3908
|
|
|
3882
3909
|
set((s) => {
|
|
3883
3910
|
const tpsHist = [...s.labMetrics.tokensPerSecHistory, tps].slice(-10);
|
|
@@ -3891,15 +3918,15 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
3891
3918
|
labSessions: sessions,
|
|
3892
3919
|
labMetrics: {
|
|
3893
3920
|
ttft, tokensPerSec: tps, tokensPerSecHistory: tpsHist,
|
|
3894
|
-
memory:
|
|
3895
|
-
totalTokens: s.labMetrics.totalTokens + (
|
|
3896
|
-
generationTime:
|
|
3921
|
+
memory: parsed.metrics.memoryUsage || s.labMetrics.memory,
|
|
3922
|
+
totalTokens: s.labMetrics.totalTokens + (parsed.metrics.totalTokens || tokenCount),
|
|
3923
|
+
generationTime: parsed.metrics.generationTime || elapsed,
|
|
3897
3924
|
},
|
|
3898
3925
|
};
|
|
3899
3926
|
});
|
|
3900
3927
|
}
|
|
3901
|
-
if (
|
|
3902
|
-
throw new Error(
|
|
3928
|
+
if (parsed.type === 'error') {
|
|
3929
|
+
throw new Error(parsed.error || 'Inference error');
|
|
3903
3930
|
}
|
|
3904
3931
|
} catch (e) {
|
|
3905
3932
|
if (e.message && e.message !== 'Inference error' && !e.message.startsWith('HTTP ')) continue;
|
|
@@ -3908,14 +3935,24 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
3908
3935
|
}
|
|
3909
3936
|
}
|
|
3910
3937
|
|
|
3911
|
-
//
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3938
|
+
// Compute final metrics from client-side timing
|
|
3939
|
+
const elapsed = performance.now() - startTime;
|
|
3940
|
+
const ttft = firstTokenTime ? firstTokenTime - startTime : null;
|
|
3941
|
+
const tps = tokenCount > 0 && elapsed > 0 ? (tokenCount / (elapsed / 1000)) : null;
|
|
3942
|
+
if (tokenCount > 0) {
|
|
3916
3943
|
set((s) => {
|
|
3917
3944
|
const tpsHist = [...s.labMetrics.tokensPerSecHistory, tps].slice(-10);
|
|
3945
|
+
const sessions = s.labSessions.map((sess) => {
|
|
3946
|
+
if (sess.id !== sessionId) return sess;
|
|
3947
|
+
const msgs = [...sess.messages];
|
|
3948
|
+
const last = msgs[msgs.length - 1];
|
|
3949
|
+
if (!last?.metrics) {
|
|
3950
|
+
msgs[msgs.length - 1] = { ...last, metrics: { ttft, tokensPerSec: tps, tokens: tokenCount, generationTime: elapsed } };
|
|
3951
|
+
}
|
|
3952
|
+
return { ...sess, messages: msgs };
|
|
3953
|
+
});
|
|
3918
3954
|
return {
|
|
3955
|
+
labSessions: sessions,
|
|
3919
3956
|
labMetrics: { ...s.labMetrics, ttft, tokensPerSec: tps, tokensPerSecHistory: tpsHist, totalTokens: s.labMetrics.totalTokens + tokenCount, generationTime: elapsed },
|
|
3920
3957
|
};
|
|
3921
3958
|
});
|
|
@@ -4040,4 +4077,45 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
4040
4077
|
return false;
|
|
4041
4078
|
}
|
|
4042
4079
|
},
|
|
4080
|
+
|
|
4081
|
+
// ── Integration Agent Install ────────────────────────────
|
|
4082
|
+
|
|
4083
|
+
async installViaExistingAgent(integration, agentId) {
|
|
4084
|
+
const message = buildIntegrationPrompt(integration);
|
|
4085
|
+
await get().instructAgent(agentId, message);
|
|
4086
|
+
get().setActiveView('agents');
|
|
4087
|
+
get().selectAgent(agentId);
|
|
4088
|
+
},
|
|
4089
|
+
|
|
4090
|
+
async spawnIntegrationTeam(integration) {
|
|
4091
|
+
const team = await get().createTeam(integration.name);
|
|
4092
|
+
const prompt = buildIntegrationPrompt(integration);
|
|
4093
|
+
const agent = await get().spawnAgent({ role: 'planner', prompt, teamId: team.id });
|
|
4094
|
+
get().setActiveView('agents');
|
|
4095
|
+
get().selectAgent(agent.id);
|
|
4096
|
+
return agent;
|
|
4097
|
+
},
|
|
4043
4098
|
}));
|
|
4099
|
+
|
|
4100
|
+
function buildIntegrationPrompt(integration) {
|
|
4101
|
+
const lines = [
|
|
4102
|
+
`Set up the "${integration.name}" integration for this project.`,
|
|
4103
|
+
'',
|
|
4104
|
+
];
|
|
4105
|
+
if (integration.description) lines.push(`**Description:** ${integration.description}`);
|
|
4106
|
+
if (integration.npmPackage) lines.push(`**npm package:** ${integration.npmPackage}`);
|
|
4107
|
+
if (integration.authType) lines.push(`**Auth type:** ${integration.authType}`);
|
|
4108
|
+
if (integration.envKeys?.length) {
|
|
4109
|
+
lines.push('', '**Environment keys required:**');
|
|
4110
|
+
for (const k of integration.envKeys) {
|
|
4111
|
+
lines.push(`- \`${k.key}\` — ${k.label}${k.required ? ' (required)' : ''}`);
|
|
4112
|
+
}
|
|
4113
|
+
}
|
|
4114
|
+
if (integration.setupSteps?.length) {
|
|
4115
|
+
lines.push('', '**Setup steps:**');
|
|
4116
|
+
integration.setupSteps.forEach((step, i) => lines.push(`${i + 1}. ${step}`));
|
|
4117
|
+
}
|
|
4118
|
+
if (integration.setupUrl) lines.push(``, `**Setup URL:** ${integration.setupUrl}`);
|
|
4119
|
+
if (integration.agentInstructions) lines.push('', `**Agent instructions:** ${integration.agentInstructions}`);
|
|
4120
|
+
return lines.join('\n');
|
|
4121
|
+
}
|