groove-dev 0.27.140 → 0.27.141
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 +82 -16
- package/node_modules/@groove-dev/daemon/src/integrations.js +10 -0
- package/node_modules/@groove-dev/daemon/src/journalist.js +169 -0
- package/node_modules/@groove-dev/daemon/src/keeper.js +3 -3
- package/node_modules/@groove-dev/daemon/src/model-lab.js +11 -0
- package/node_modules/@groove-dev/daemon/src/process.js +76 -0
- package/node_modules/@groove-dev/daemon/src/validate.js +8 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-A4e1gIDh.css +1 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-P1hsM27-.js +8696 -0
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +7 -2
- 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 +160 -12
- package/node_modules/@groove-dev/gui/src/components/editor/ai-panel.jsx +77 -6
- 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/terminal-panel.jsx +152 -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 +110 -32
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +114 -56
- 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 +82 -16
- package/packages/daemon/src/integrations.js +10 -0
- package/packages/daemon/src/journalist.js +169 -0
- package/packages/daemon/src/keeper.js +3 -3
- package/packages/daemon/src/model-lab.js +11 -0
- package/packages/daemon/src/process.js +76 -0
- package/packages/daemon/src/validate.js +8 -0
- package/packages/gui/dist/assets/index-A4e1gIDh.css +1 -0
- package/packages/gui/dist/assets/index-P1hsM27-.js +8696 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/agents/agent-chat.jsx +3 -3
- package/packages/gui/src/components/agents/agent-file-tree.jsx +7 -2
- package/packages/gui/src/components/agents/code-review.jsx +5 -4
- package/packages/gui/src/components/agents/workspace-mode.jsx +160 -12
- package/packages/gui/src/components/editor/ai-panel.jsx +77 -6
- 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/terminal-panel.jsx +152 -3
- package/packages/gui/src/components/marketplace/integration-wizard.jsx +223 -18
- package/packages/gui/src/stores/groove.js +110 -32
- package/packages/gui/src/views/agents.jsx +114 -56
- 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/packages/gui/dist/assets/index-BV9CAiw1.css +0 -1
- package/packages/gui/dist/assets/index-DK6UIz0n.js +0 -8698
|
@@ -4,18 +4,18 @@ import { useState, useEffect, useRef } from 'react';
|
|
|
4
4
|
import { useGrooveStore } from '../../stores/groove';
|
|
5
5
|
import { Dialog, DialogContent } from '../ui/dialog';
|
|
6
6
|
import { Button } from '../ui/button';
|
|
7
|
-
import { Save
|
|
7
|
+
import { Save } from 'lucide-react';
|
|
8
8
|
|
|
9
9
|
const COMMANDS = [
|
|
10
|
-
{ cmd: '
|
|
11
|
-
{ cmd: '
|
|
12
|
-
{ cmd: '
|
|
13
|
-
{ cmd: '
|
|
14
|
-
{ cmd: '
|
|
15
|
-
{ cmd: '
|
|
16
|
-
{ cmd: '
|
|
17
|
-
{ cmd: '
|
|
18
|
-
{ cmd: '[instruct]', args: '',
|
|
10
|
+
{ cmd: 'save', args: '#tag', desc: 'Save the message and send it to the agent' },
|
|
11
|
+
{ cmd: 'append', args: '#tag', desc: 'Add to an existing memory and send to agent' },
|
|
12
|
+
{ cmd: 'update', args: '#tag', desc: 'Open the editor to modify a memory in place' },
|
|
13
|
+
{ cmd: 'delete', args: '#tag', desc: 'Remove a memory permanently' },
|
|
14
|
+
{ cmd: 'view', args: '#tag', desc: 'Read a memory in the viewer' },
|
|
15
|
+
{ cmd: 'read', args: '#tag1 #tag2 ...', desc: 'Send memory content to the agent — chat stays clean' },
|
|
16
|
+
{ cmd: 'doc', args: '#tag', desc: 'AI synthesizes the full conversation into a document' },
|
|
17
|
+
{ cmd: 'link', args: '#tag path/to/doc', desc: 'Link a memory to a NORTHSTAR or external document' },
|
|
18
|
+
{ cmd: '[instruct]', args: '', desc: 'Show this command reference' },
|
|
19
19
|
];
|
|
20
20
|
|
|
21
21
|
function KeeperEditorModal() {
|
|
@@ -1,7 +1,87 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
-
import { useRef, useCallback, useState } from 'react';
|
|
3
|
-
import { Maximize2, Minimize2, Plus, X, Terminal } from 'lucide-react';
|
|
2
|
+
import { useRef, useCallback, useState, useEffect } from 'react';
|
|
3
|
+
import { Maximize2, Minimize2, Plus, X, Terminal, Send, ChevronDown, Sparkles } from 'lucide-react';
|
|
4
4
|
import { cn } from '../../lib/cn';
|
|
5
|
+
import { useGrooveStore } from '../../stores/groove';
|
|
6
|
+
import { Tooltip } from '../ui/tooltip';
|
|
7
|
+
|
|
8
|
+
function AgentPicker({ onSelect, onClose }) {
|
|
9
|
+
const ref = useRef(null);
|
|
10
|
+
const agents = useGrooveStore((s) => s.agents);
|
|
11
|
+
const teams = useGrooveStore((s) => s.teams);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
function handleClick(e) {
|
|
15
|
+
if (ref.current && !ref.current.contains(e.target)) onClose();
|
|
16
|
+
}
|
|
17
|
+
function handleKey(e) {
|
|
18
|
+
if (e.key === 'Escape') onClose();
|
|
19
|
+
}
|
|
20
|
+
document.addEventListener('mousedown', handleClick);
|
|
21
|
+
document.addEventListener('keydown', handleKey);
|
|
22
|
+
return () => {
|
|
23
|
+
document.removeEventListener('mousedown', handleClick);
|
|
24
|
+
document.removeEventListener('keydown', handleKey);
|
|
25
|
+
};
|
|
26
|
+
}, [onClose]);
|
|
27
|
+
|
|
28
|
+
const running = agents.filter((a) => a.status === 'running' || a.status === 'starting');
|
|
29
|
+
const stopped = agents.filter((a) => a.status !== 'running' && a.status !== 'starting');
|
|
30
|
+
|
|
31
|
+
function teamName(teamId) {
|
|
32
|
+
const team = teams.find((t) => t.id === teamId);
|
|
33
|
+
return team?.name || 'Default';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
ref={ref}
|
|
39
|
+
className="absolute bottom-full right-0 mb-1 z-50 min-w-[220px] max-h-[300px] overflow-y-auto py-1 bg-surface-2 border border-border rounded-lg shadow-xl"
|
|
40
|
+
>
|
|
41
|
+
{running.length > 0 && (
|
|
42
|
+
<>
|
|
43
|
+
<div className="px-3 py-1 text-2xs text-text-4 font-sans font-medium uppercase tracking-wider">Active</div>
|
|
44
|
+
{running.map((agent) => (
|
|
45
|
+
<button
|
|
46
|
+
key={agent.id}
|
|
47
|
+
onClick={() => onSelect(agent.id)}
|
|
48
|
+
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs font-sans text-text-1 hover:bg-surface-5 cursor-pointer transition-colors text-left"
|
|
49
|
+
>
|
|
50
|
+
<span className="w-1.5 h-1.5 rounded-full bg-success flex-shrink-0" />
|
|
51
|
+
<span className="truncate flex-1">{agent.name}</span>
|
|
52
|
+
<span className="text-2xs text-text-4">{teamName(agent.teamId)}</span>
|
|
53
|
+
</button>
|
|
54
|
+
))}
|
|
55
|
+
</>
|
|
56
|
+
)}
|
|
57
|
+
{stopped.length > 0 && (
|
|
58
|
+
<>
|
|
59
|
+
<div className="px-3 py-1 text-2xs text-text-4 font-sans font-medium uppercase tracking-wider">
|
|
60
|
+
{running.length > 0 ? 'Other' : 'Agents'}
|
|
61
|
+
</div>
|
|
62
|
+
{stopped.slice(0, 10).map((agent) => (
|
|
63
|
+
<button
|
|
64
|
+
key={agent.id}
|
|
65
|
+
onClick={() => onSelect(agent.id)}
|
|
66
|
+
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs font-sans text-text-2 hover:bg-surface-5 cursor-pointer transition-colors text-left"
|
|
67
|
+
>
|
|
68
|
+
<span className={cn('w-1.5 h-1.5 rounded-full flex-shrink-0',
|
|
69
|
+
agent.status === 'completed' ? 'bg-accent' : agent.status === 'crashed' ? 'bg-danger' : 'bg-text-4',
|
|
70
|
+
)} />
|
|
71
|
+
<span className="truncate flex-1">{agent.name}</span>
|
|
72
|
+
<span className="text-2xs text-text-4">{teamName(agent.teamId)}</span>
|
|
73
|
+
</button>
|
|
74
|
+
))}
|
|
75
|
+
</>
|
|
76
|
+
)}
|
|
77
|
+
{agents.length === 0 && (
|
|
78
|
+
<div className="px-3 py-3 text-xs text-text-4 font-sans text-center">
|
|
79
|
+
No agents available
|
|
80
|
+
</div>
|
|
81
|
+
)}
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
5
85
|
|
|
6
86
|
export function TerminalPanel({
|
|
7
87
|
children,
|
|
@@ -18,13 +98,22 @@ export function TerminalPanel({
|
|
|
18
98
|
onMinimize,
|
|
19
99
|
onClose,
|
|
20
100
|
onRenameTab,
|
|
101
|
+
selectedText,
|
|
21
102
|
}) {
|
|
22
103
|
const dragging = useRef(false);
|
|
23
104
|
const [renamingId, setRenamingId] = useState(null);
|
|
24
105
|
const [renameValue, setRenameValue] = useState('');
|
|
106
|
+
const [showPicker, setShowPicker] = useState(false);
|
|
25
107
|
const startY = useRef(0);
|
|
26
108
|
const startH = useRef(0);
|
|
27
109
|
|
|
110
|
+
const activeAgent = useGrooveStore((s) => s.editorSelectedAgent);
|
|
111
|
+
const agents = useGrooveStore((s) => s.agents);
|
|
112
|
+
const attachSnippet = useGrooveStore((s) => s.attachSnippet);
|
|
113
|
+
|
|
114
|
+
const agent = agents.find((a) => a.id === activeAgent);
|
|
115
|
+
const hasSelection = selectedText && selectedText.trim().length > 0;
|
|
116
|
+
|
|
28
117
|
const onMouseDown = useCallback((e) => {
|
|
29
118
|
if (fullHeight) return;
|
|
30
119
|
e.preventDefault();
|
|
@@ -49,6 +138,21 @@ export function TerminalPanel({
|
|
|
49
138
|
document.addEventListener('mouseup', onMouseUp);
|
|
50
139
|
}, [height, onHeightChange, fullHeight]);
|
|
51
140
|
|
|
141
|
+
function sendToAgent(agentId) {
|
|
142
|
+
if (!agentId || !selectedText?.trim()) return;
|
|
143
|
+
setShowPicker(false);
|
|
144
|
+
useGrooveStore.setState({ editorSelectedAgent: agentId });
|
|
145
|
+
attachSnippet({ type: 'terminal', code: selectedText.trim() });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function handleSendClick() {
|
|
149
|
+
if (activeAgent) {
|
|
150
|
+
sendToAgent(activeAgent);
|
|
151
|
+
} else {
|
|
152
|
+
setShowPicker(true);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
52
156
|
const tabList = tabs || [{ id: 'default', label: 'Terminal' }];
|
|
53
157
|
|
|
54
158
|
return (
|
|
@@ -116,8 +220,53 @@ export function TerminalPanel({
|
|
|
116
220
|
</button>
|
|
117
221
|
</div>
|
|
118
222
|
|
|
119
|
-
{/* Window controls */}
|
|
223
|
+
{/* Send to Agent + Window controls */}
|
|
120
224
|
<div className="flex items-center gap-0.5 flex-shrink-0 ml-2">
|
|
225
|
+
{/* Send to Agent */}
|
|
226
|
+
{hasSelection && (
|
|
227
|
+
<div className="relative flex items-center">
|
|
228
|
+
<Tooltip content={activeAgent ? `Send to ${agent?.name || 'agent'}` : 'Send to agent'} side="top">
|
|
229
|
+
<button
|
|
230
|
+
onClick={handleSendClick}
|
|
231
|
+
disabled={sending}
|
|
232
|
+
className={cn(
|
|
233
|
+
'flex items-center gap-1.5 px-2 py-1 rounded text-xs font-sans cursor-pointer transition-colors mr-1',
|
|
234
|
+
'bg-accent/15 text-accent hover:bg-accent/25 disabled:opacity-50',
|
|
235
|
+
)}
|
|
236
|
+
>
|
|
237
|
+
{activeAgent ? (
|
|
238
|
+
<>
|
|
239
|
+
<Send size={11} />
|
|
240
|
+
<span className="text-2xs max-w-[80px] truncate">{agent?.name || 'Agent'}</span>
|
|
241
|
+
</>
|
|
242
|
+
) : (
|
|
243
|
+
<>
|
|
244
|
+
<Sparkles size={11} />
|
|
245
|
+
<span className="text-2xs">Agent</span>
|
|
246
|
+
<ChevronDown size={9} />
|
|
247
|
+
</>
|
|
248
|
+
)}
|
|
249
|
+
</button>
|
|
250
|
+
</Tooltip>
|
|
251
|
+
{!activeAgent && hasSelection && (
|
|
252
|
+
<Tooltip content="Pick agent" side="top">
|
|
253
|
+
<button
|
|
254
|
+
onClick={() => setShowPicker(!showPicker)}
|
|
255
|
+
className="p-1 rounded text-text-3 hover:text-accent hover:bg-accent/10 cursor-pointer transition-colors mr-1"
|
|
256
|
+
>
|
|
257
|
+
<ChevronDown size={10} />
|
|
258
|
+
</button>
|
|
259
|
+
</Tooltip>
|
|
260
|
+
)}
|
|
261
|
+
{showPicker && (
|
|
262
|
+
<AgentPicker
|
|
263
|
+
onSelect={(id) => sendToAgent(id)}
|
|
264
|
+
onClose={() => setShowPicker(false)}
|
|
265
|
+
/>
|
|
266
|
+
)}
|
|
267
|
+
</div>
|
|
268
|
+
)}
|
|
269
|
+
|
|
121
270
|
{fullHeight ? (
|
|
122
271
|
<button
|
|
123
272
|
onClick={onMinimize}
|
|
@@ -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}
|