groove-dev 0.27.8 → 0.27.11
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/daemon/src/api.js +460 -25
- package/node_modules/@groove-dev/daemon/src/index.js +7 -0
- package/node_modules/@groove-dev/daemon/src/introducer.js +72 -4
- package/node_modules/@groove-dev/daemon/src/journalist.js +66 -11
- package/node_modules/@groove-dev/daemon/src/process.js +67 -7
- package/node_modules/@groove-dev/daemon/src/registry.js +1 -1
- package/node_modules/@groove-dev/daemon/src/repo-import.js +541 -0
- package/node_modules/@groove-dev/daemon/src/rotator.js +28 -1
- package/node_modules/@groove-dev/daemon/src/supervisor.js +2 -1
- package/node_modules/@groove-dev/daemon/src/tunnel-manager.js +504 -0
- package/node_modules/@groove-dev/daemon/src/validate.js +13 -0
- package/node_modules/@groove-dev/daemon/test/journalist.test.js +5 -4
- package/node_modules/@groove-dev/daemon/test/rotator.test.js +4 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-BE6lYcd7.css +1 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-zdzOLAZM.js +677 -0
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/src/app.css +14 -0
- package/node_modules/@groove-dev/gui/src/app.jsx +13 -0
- package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +130 -1
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +2 -2
- package/node_modules/@groove-dev/gui/src/components/agents/agent-mdfiles.jsx +43 -1
- package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +141 -1
- package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +4 -4
- package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +4 -4
- package/node_modules/@groove-dev/gui/src/components/layout/app-shell.jsx +7 -1
- package/node_modules/@groove-dev/gui/src/components/layout/breadcrumb-bar.jsx +26 -8
- package/node_modules/@groove-dev/gui/src/components/layout/command-palette.jsx +14 -4
- package/node_modules/@groove-dev/gui/src/components/layout/status-bar.jsx +46 -11
- package/node_modules/@groove-dev/gui/src/components/marketplace/repo-card.jsx +64 -0
- package/node_modules/@groove-dev/gui/src/components/marketplace/repo-import.jsx +363 -0
- package/node_modules/@groove-dev/gui/src/components/marketplace/repo-nuke-dialog.jsx +68 -0
- package/node_modules/@groove-dev/gui/src/components/pro/pro-gate.jsx +22 -0
- package/node_modules/@groove-dev/gui/src/components/pro/upgrade-card.jsx +48 -0
- package/node_modules/@groove-dev/gui/src/components/settings/quick-connect.jsx +129 -0
- package/node_modules/@groove-dev/gui/src/components/settings/remote-server-card.jsx +243 -0
- package/node_modules/@groove-dev/gui/src/components/settings/server-dialog.jsx +192 -0
- package/node_modules/@groove-dev/gui/src/components/ui/approval-modal.jsx +63 -0
- package/node_modules/@groove-dev/gui/src/components/ui/toast.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/lib/edition.js +4 -0
- package/node_modules/@groove-dev/gui/src/lib/electron.js +17 -0
- package/node_modules/@groove-dev/gui/src/lib/status.js +1 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +139 -6
- package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +38 -39
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +82 -0
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +66 -0
- package/node_modules/@groove-dev/gui/vite.config.js +3 -0
- package/package.json +7 -2
- package/packages/daemon/src/api.js +460 -25
- package/packages/daemon/src/index.js +7 -0
- package/packages/daemon/src/introducer.js +72 -4
- package/packages/daemon/src/journalist.js +66 -11
- package/packages/daemon/src/process.js +67 -7
- package/packages/daemon/src/registry.js +1 -1
- package/packages/daemon/src/repo-import.js +541 -0
- package/packages/daemon/src/rotator.js +28 -1
- package/packages/daemon/src/supervisor.js +2 -1
- package/packages/daemon/src/tunnel-manager.js +504 -0
- package/packages/daemon/src/validate.js +13 -0
- package/packages/gui/dist/assets/index-BE6lYcd7.css +1 -0
- package/packages/gui/dist/assets/index-zdzOLAZM.js +677 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-html.js +3 -3
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-javascript.js +2 -2
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-markdown.js +3 -3
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-python.js +5 -5
- package/packages/gui/node_modules/.vite/deps/@radix-ui_react-dialog.js +3 -3
- package/packages/gui/node_modules/.vite/deps/@radix-ui_react-scroll-area.js +1 -1
- package/packages/gui/node_modules/.vite/deps/@radix-ui_react-tabs.js +5 -5
- package/packages/gui/node_modules/.vite/deps/@radix-ui_react-tooltip.js +3 -3
- package/packages/gui/node_modules/.vite/deps/_metadata.json +53 -53
- package/packages/gui/node_modules/.vite/deps/{chunk-WYSQD5ZG.js → chunk-DH7AESXW.js} +2 -2
- package/packages/gui/node_modules/.vite/deps/{chunk-KXLIKZFX.js → chunk-GFE3G4IN.js} +133 -133
- package/packages/gui/node_modules/.vite/deps/chunk-GFE3G4IN.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/{chunk-3LBP22MX.js → chunk-LKZVMLRH.js} +6 -6
- package/packages/gui/node_modules/.vite/deps/{chunk-J6DMOQWP.js → chunk-MCVDVNE5.js} +2 -2
- package/packages/gui/node_modules/.vite/deps/{chunk-3Q7HT7ZF.js → chunk-SPKVQGZX.js} +6 -6
- package/packages/gui/src/app.css +14 -0
- package/packages/gui/src/app.jsx +13 -0
- package/packages/gui/src/components/agents/agent-config.jsx +130 -1
- package/packages/gui/src/components/agents/agent-feed.jsx +2 -2
- package/packages/gui/src/components/agents/agent-mdfiles.jsx +43 -1
- package/packages/gui/src/components/agents/spawn-wizard.jsx +141 -1
- package/packages/gui/src/components/dashboard/fleet-panel.jsx +3 -3
- package/packages/gui/src/components/dashboard/intel-panel.jsx +4 -4
- package/packages/gui/src/components/dashboard/routing-chart.jsx +3 -3
- package/packages/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
- package/packages/gui/src/components/layout/activity-bar.jsx +4 -4
- package/packages/gui/src/components/layout/app-shell.jsx +7 -1
- package/packages/gui/src/components/layout/breadcrumb-bar.jsx +26 -8
- package/packages/gui/src/components/layout/command-palette.jsx +14 -4
- package/packages/gui/src/components/layout/status-bar.jsx +46 -11
- package/packages/gui/src/components/marketplace/repo-card.jsx +64 -0
- package/packages/gui/src/components/marketplace/repo-import.jsx +363 -0
- package/packages/gui/src/components/marketplace/repo-nuke-dialog.jsx +68 -0
- package/packages/gui/src/components/pro/pro-gate.jsx +22 -0
- package/packages/gui/src/components/pro/upgrade-card.jsx +48 -0
- package/packages/gui/src/components/settings/quick-connect.jsx +129 -0
- package/packages/gui/src/components/settings/remote-server-card.jsx +243 -0
- package/packages/gui/src/components/settings/server-dialog.jsx +192 -0
- package/packages/gui/src/components/ui/approval-modal.jsx +63 -0
- package/packages/gui/src/components/ui/toast.jsx +1 -1
- package/packages/gui/src/lib/edition.js +4 -0
- package/packages/gui/src/lib/electron.js +17 -0
- package/packages/gui/src/lib/status.js +1 -0
- package/packages/gui/src/stores/groove.js +139 -6
- package/packages/gui/src/views/dashboard.jsx +38 -39
- package/packages/gui/src/views/marketplace.jsx +82 -0
- package/packages/gui/src/views/settings.jsx +66 -0
- package/packages/gui/vite.config.js +3 -0
- package/integrations/FEDERATION_PLAN.md +0 -583
- package/integrations/VOICE_PLAN.md +0 -232
- package/node_modules/@groove-dev/gui/dist/assets/index-CwmR3-HY.css +0 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-DiCjVtQL.js +0 -652
- package/packages/gui/dist/assets/index-CwmR3-HY.css +0 -1
- package/packages/gui/dist/assets/index-DiCjVtQL.js +0 -652
- package/packages/gui/node_modules/.vite/deps/chunk-KXLIKZFX.js.map +0 -7
- package/test-slack.mjs +0 -28
- /package/packages/gui/node_modules/.vite/deps/{chunk-WYSQD5ZG.js.map → chunk-DH7AESXW.js.map} +0 -0
- /package/packages/gui/node_modules/.vite/deps/{chunk-3LBP22MX.js.map → chunk-LKZVMLRH.js.map} +0 -0
- /package/packages/gui/node_modules/.vite/deps/{chunk-J6DMOQWP.js.map → chunk-MCVDVNE5.js.map} +0 -0
- /package/packages/gui/node_modules/.vite/deps/{chunk-3Q7HT7ZF.js.map → chunk-SPKVQGZX.js.map} +0 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { Dialog, DialogContent } from '../ui/dialog';
|
|
4
|
+
import { Button } from '../ui/button';
|
|
5
|
+
import { FolderBrowser } from '../agents/folder-browser';
|
|
6
|
+
import { cn } from '../../lib/cn';
|
|
7
|
+
import { FolderSearch } from 'lucide-react';
|
|
8
|
+
|
|
9
|
+
export function ServerDialog({ open, onOpenChange, server, onSave }) {
|
|
10
|
+
const [name, setName] = useState('');
|
|
11
|
+
const [host, setHost] = useState('');
|
|
12
|
+
const [user, setUser] = useState('');
|
|
13
|
+
const [sshPort, setSshPort] = useState(22);
|
|
14
|
+
const [sshKeyPath, setSshKeyPath] = useState('');
|
|
15
|
+
const [autoStart, setAutoStart] = useState(false);
|
|
16
|
+
const [autoConnect, setAutoConnect] = useState(false);
|
|
17
|
+
const [keyBrowserOpen, setKeyBrowserOpen] = useState(false);
|
|
18
|
+
const [saving, setSaving] = useState(false);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (open) {
|
|
22
|
+
if (server) {
|
|
23
|
+
setName(server.name || '');
|
|
24
|
+
setHost(server.host || '');
|
|
25
|
+
setUser(server.user || '');
|
|
26
|
+
setSshPort(server.port || 22);
|
|
27
|
+
setSshKeyPath(server.sshKeyPath || '');
|
|
28
|
+
setAutoStart(server.autoStart || false);
|
|
29
|
+
setAutoConnect(server.autoConnect || false);
|
|
30
|
+
} else {
|
|
31
|
+
setName('');
|
|
32
|
+
setHost('');
|
|
33
|
+
setUser('');
|
|
34
|
+
setSshPort(22);
|
|
35
|
+
setSshKeyPath('');
|
|
36
|
+
setAutoStart(false);
|
|
37
|
+
setAutoConnect(false);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}, [open, server]);
|
|
41
|
+
|
|
42
|
+
async function handleSave() {
|
|
43
|
+
if (!name.trim() || !host.trim() || !user.trim()) return;
|
|
44
|
+
setSaving(true);
|
|
45
|
+
try {
|
|
46
|
+
const data = {
|
|
47
|
+
name: name.trim(),
|
|
48
|
+
host: host.trim(),
|
|
49
|
+
user: user.trim(),
|
|
50
|
+
port: sshPort,
|
|
51
|
+
sshKeyPath: sshKeyPath.trim(),
|
|
52
|
+
autoStart,
|
|
53
|
+
autoConnect,
|
|
54
|
+
};
|
|
55
|
+
if (server?.id) data.id = server.id;
|
|
56
|
+
await onSave(data);
|
|
57
|
+
onOpenChange(false);
|
|
58
|
+
} catch {}
|
|
59
|
+
setSaving(false);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
64
|
+
<DialogContent
|
|
65
|
+
title={server ? `Edit ${server.name}` : 'Add Remote Server'}
|
|
66
|
+
description="Configure SSH connection to a remote server"
|
|
67
|
+
className="max-w-[460px]"
|
|
68
|
+
>
|
|
69
|
+
<div className="px-5 py-4 space-y-4">
|
|
70
|
+
{/* Name */}
|
|
71
|
+
<div>
|
|
72
|
+
<label className="text-2xs font-semibold text-text-2 font-sans mb-1.5 block">Name</label>
|
|
73
|
+
<input
|
|
74
|
+
value={name}
|
|
75
|
+
onChange={(e) => setName(e.target.value)}
|
|
76
|
+
placeholder="api-vps"
|
|
77
|
+
className="w-full h-9 px-3 text-xs bg-surface-0 border border-border rounded-md text-text-0 font-sans placeholder:text-text-4 focus:outline-none focus:ring-1 focus:ring-accent"
|
|
78
|
+
autoFocus
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
{/* Host */}
|
|
83
|
+
<div>
|
|
84
|
+
<label className="text-2xs font-semibold text-text-2 font-sans mb-1.5 block">Host</label>
|
|
85
|
+
<input
|
|
86
|
+
value={host}
|
|
87
|
+
onChange={(e) => setHost(e.target.value)}
|
|
88
|
+
placeholder="165.22.180.45 or hostname"
|
|
89
|
+
className="w-full h-9 px-3 text-xs bg-surface-0 border border-border rounded-md text-text-0 font-mono placeholder:text-text-4 focus:outline-none focus:ring-1 focus:ring-accent"
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
{/* User + SSH Port */}
|
|
94
|
+
<div className="flex gap-3">
|
|
95
|
+
<div className="flex-1">
|
|
96
|
+
<label className="text-2xs font-semibold text-text-2 font-sans mb-1.5 block">User</label>
|
|
97
|
+
<input
|
|
98
|
+
value={user}
|
|
99
|
+
onChange={(e) => setUser(e.target.value)}
|
|
100
|
+
placeholder="root"
|
|
101
|
+
className="w-full h-9 px-3 text-xs bg-surface-0 border border-border rounded-md text-text-0 font-mono placeholder:text-text-4 focus:outline-none focus:ring-1 focus:ring-accent"
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
<div className="w-24">
|
|
105
|
+
<label className="text-2xs font-semibold text-text-2 font-sans mb-1.5 block">SSH Port</label>
|
|
106
|
+
<input
|
|
107
|
+
value={sshPort}
|
|
108
|
+
onChange={(e) => setSshPort(Number(e.target.value) || 22)}
|
|
109
|
+
type="number"
|
|
110
|
+
className="w-full h-9 px-3 text-xs bg-surface-0 border border-border rounded-md text-text-0 font-mono placeholder:text-text-4 focus:outline-none focus:ring-1 focus:ring-accent"
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
{/* SSH Key */}
|
|
116
|
+
<div>
|
|
117
|
+
<label className="text-2xs font-semibold text-text-2 font-sans mb-1.5 block">SSH Key</label>
|
|
118
|
+
<div className="flex items-center gap-1.5">
|
|
119
|
+
<input
|
|
120
|
+
value={sshKeyPath}
|
|
121
|
+
onChange={(e) => setSshKeyPath(e.target.value)}
|
|
122
|
+
placeholder="~/.ssh/id_ed25519"
|
|
123
|
+
className="flex-1 h-9 px-3 text-xs bg-surface-0 border border-border rounded-md text-text-0 font-mono placeholder:text-text-4 focus:outline-none focus:ring-1 focus:ring-accent"
|
|
124
|
+
/>
|
|
125
|
+
<Button
|
|
126
|
+
variant="secondary"
|
|
127
|
+
size="sm"
|
|
128
|
+
onClick={() => setKeyBrowserOpen(true)}
|
|
129
|
+
className="h-9 px-2.5 flex-shrink-0"
|
|
130
|
+
>
|
|
131
|
+
<FolderSearch size={13} />
|
|
132
|
+
</Button>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
{/* Toggles */}
|
|
137
|
+
<div className="space-y-3 pt-1">
|
|
138
|
+
<label className="flex items-center justify-between cursor-pointer">
|
|
139
|
+
<span className="text-xs text-text-2 font-sans">Auto-start daemon on connect</span>
|
|
140
|
+
<ToggleSwitch value={autoStart} onChange={setAutoStart} />
|
|
141
|
+
</label>
|
|
142
|
+
<label className="flex items-center justify-between cursor-pointer">
|
|
143
|
+
<span className="text-xs text-text-2 font-sans">Auto-connect on Groove launch</span>
|
|
144
|
+
<ToggleSwitch value={autoConnect} onChange={setAutoConnect} />
|
|
145
|
+
</label>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
{/* Actions */}
|
|
149
|
+
<div className="flex justify-end gap-2 pt-2">
|
|
150
|
+
<Button variant="ghost" size="sm" onClick={() => onOpenChange(false)} className="h-8 text-xs px-4 text-text-3">
|
|
151
|
+
Cancel
|
|
152
|
+
</Button>
|
|
153
|
+
<Button
|
|
154
|
+
variant="primary"
|
|
155
|
+
size="sm"
|
|
156
|
+
onClick={handleSave}
|
|
157
|
+
disabled={!name.trim() || !host.trim() || !user.trim() || saving}
|
|
158
|
+
className="h-8 text-xs px-4"
|
|
159
|
+
>
|
|
160
|
+
{saving ? 'Saving...' : 'Save'}
|
|
161
|
+
</Button>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
{/* File browser for SSH key */}
|
|
166
|
+
<FolderBrowser
|
|
167
|
+
open={keyBrowserOpen}
|
|
168
|
+
onOpenChange={setKeyBrowserOpen}
|
|
169
|
+
currentPath={sshKeyPath || '~/.ssh'}
|
|
170
|
+
onSelect={(path) => setSshKeyPath(path)}
|
|
171
|
+
/>
|
|
172
|
+
</DialogContent>
|
|
173
|
+
</Dialog>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function ToggleSwitch({ value, onChange }) {
|
|
178
|
+
return (
|
|
179
|
+
<button
|
|
180
|
+
onClick={() => onChange(!value)}
|
|
181
|
+
className={cn(
|
|
182
|
+
'w-9 h-5 rounded-full p-0.5 transition-colors cursor-pointer',
|
|
183
|
+
value ? 'bg-accent' : 'bg-surface-5',
|
|
184
|
+
)}
|
|
185
|
+
>
|
|
186
|
+
<div className={cn(
|
|
187
|
+
'w-4 h-4 rounded-full bg-white shadow-sm transition-transform',
|
|
188
|
+
value ? 'translate-x-4' : 'translate-x-0',
|
|
189
|
+
)} />
|
|
190
|
+
</button>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
import { AnimatePresence, motion } from 'framer-motion';
|
|
3
|
+
import { ShieldCheck, ShieldX, AlertTriangle } from 'lucide-react';
|
|
4
|
+
import { Button } from '../ui/button';
|
|
5
|
+
import { useGrooveStore } from '../../stores/groove';
|
|
6
|
+
|
|
7
|
+
export function ApprovalModal() {
|
|
8
|
+
const pendingApprovals = useGrooveStore((s) => s.pendingApprovals);
|
|
9
|
+
const approveRequest = useGrooveStore((s) => s.approveRequest);
|
|
10
|
+
const rejectRequest = useGrooveStore((s) => s.rejectRequest);
|
|
11
|
+
|
|
12
|
+
if (!pendingApprovals?.length) return null;
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className="fixed bottom-10 left-1/2 -translate-x-1/2 z-50 w-full max-w-md flex flex-col gap-2 px-4">
|
|
16
|
+
<AnimatePresence>
|
|
17
|
+
{pendingApprovals.map((approval) => (
|
|
18
|
+
<motion.div
|
|
19
|
+
key={approval.id}
|
|
20
|
+
initial={{ y: 20, opacity: 0 }}
|
|
21
|
+
animate={{ y: 0, opacity: 1 }}
|
|
22
|
+
exit={{ y: 20, opacity: 0 }}
|
|
23
|
+
transition={{ duration: 0.2 }}
|
|
24
|
+
className="rounded-lg border border-accent/30 bg-surface-2/95 backdrop-blur-md shadow-xl shadow-accent/5 overflow-hidden"
|
|
25
|
+
>
|
|
26
|
+
<div className="px-4 py-3 flex items-start gap-3">
|
|
27
|
+
<AlertTriangle size={16} className="text-warning shrink-0 mt-0.5" />
|
|
28
|
+
<div className="flex-1 min-w-0">
|
|
29
|
+
<p className="text-sm font-semibold text-text-0 font-sans truncate">
|
|
30
|
+
{approval.agentName || 'Agent'} needs approval
|
|
31
|
+
</p>
|
|
32
|
+
{approval.action?.description && (
|
|
33
|
+
<p className="text-2xs text-text-3 font-sans mt-0.5 line-clamp-2">
|
|
34
|
+
{approval.action.description}
|
|
35
|
+
</p>
|
|
36
|
+
)}
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
<div className="px-4 py-2.5 border-t border-border-subtle flex items-center justify-end gap-2">
|
|
40
|
+
<Button
|
|
41
|
+
size="sm"
|
|
42
|
+
variant="ghost"
|
|
43
|
+
className="text-danger hover:bg-danger/10"
|
|
44
|
+
onClick={() => rejectRequest(approval.id)}
|
|
45
|
+
>
|
|
46
|
+
<ShieldX size={14} className="mr-1" />
|
|
47
|
+
Reject
|
|
48
|
+
</Button>
|
|
49
|
+
<Button
|
|
50
|
+
size="sm"
|
|
51
|
+
variant="accent"
|
|
52
|
+
onClick={() => approveRequest(approval.id)}
|
|
53
|
+
>
|
|
54
|
+
<ShieldCheck size={14} className="mr-1" />
|
|
55
|
+
Approve
|
|
56
|
+
</Button>
|
|
57
|
+
</div>
|
|
58
|
+
</motion.div>
|
|
59
|
+
))}
|
|
60
|
+
</AnimatePresence>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -78,7 +78,7 @@ export function ToastContainer() {
|
|
|
78
78
|
const toasts = useGrooveStore((s) => s.toasts);
|
|
79
79
|
|
|
80
80
|
return (
|
|
81
|
-
<div className="fixed bottom-
|
|
81
|
+
<div className="fixed bottom-10 left-[60px] z-[100] flex flex-col-reverse gap-2">
|
|
82
82
|
<AnimatePresence mode="popLayout">
|
|
83
83
|
{toasts.slice(-3).map((toast) => (
|
|
84
84
|
<ToastItem key={toast.id} toast={toast} />
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
|
|
3
|
+
export function isElectron() {
|
|
4
|
+
return !!(window.groove || navigator.userAgent.includes('Electron'));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function getPlatform() {
|
|
8
|
+
return window.groove?.platform || 'browser';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function openExternal(url) {
|
|
12
|
+
if (window.groove) {
|
|
13
|
+
window.groove.openExternal(url);
|
|
14
|
+
} else {
|
|
15
|
+
window.open(url, '_blank');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -42,6 +42,7 @@ export const ROLE_COLORS = {
|
|
|
42
42
|
analyst: { bg: 'rgba(198, 120, 221, 0.12)', text: '#c678dd', border: '#c678dd' },
|
|
43
43
|
creative: { bg: 'rgba(229, 192, 123, 0.12)', text: '#e5c07b', border: '#e5c07b' },
|
|
44
44
|
slides: { bg: 'rgba(209, 154, 102, 0.12)', text: '#d19a66', border: '#d19a66' },
|
|
45
|
+
chat: { bg: 'rgba(198, 120, 221, 0.12)', text: '#c678dd', border: '#c678dd' },
|
|
45
46
|
};
|
|
46
47
|
|
|
47
48
|
export function roleColor(role) {
|
|
@@ -7,6 +7,7 @@ import { api } from '../lib/api';
|
|
|
7
7
|
const WS_URL = `ws://${window.location.hostname}:${window.location.port || 31415}`;
|
|
8
8
|
|
|
9
9
|
let toastCounter = 0;
|
|
10
|
+
let plannerPollInterval = null;
|
|
10
11
|
|
|
11
12
|
function loadJSON(key, fallback = {}) {
|
|
12
13
|
try { return JSON.parse(localStorage.getItem(key) || JSON.stringify(fallback)); }
|
|
@@ -46,6 +47,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
46
47
|
detailPanel: null, // null | { type: 'agent', agentId } | { type: 'spawn' } | { type: 'journalist' }
|
|
47
48
|
teamDetailPanels: {}, // { [teamId]: detailPanel } — persists panel state per team
|
|
48
49
|
commandPaletteOpen: false,
|
|
50
|
+
quickConnectOpen: false,
|
|
49
51
|
|
|
50
52
|
// ── Node expansion (click-to-open persistent panels) ───────
|
|
51
53
|
expandedNodes: loadJSON('groove:expandedNodes'),
|
|
@@ -79,6 +81,14 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
79
81
|
// ── Toasts ────────────────────────────────────────────────
|
|
80
82
|
toasts: [],
|
|
81
83
|
|
|
84
|
+
// ── Tunnels ────────────────────────────────────────────────
|
|
85
|
+
savedTunnels: [],
|
|
86
|
+
activeTunnelId: null,
|
|
87
|
+
|
|
88
|
+
// ── GitHub Repo Import ────────────────────────────────────
|
|
89
|
+
importedRepos: [],
|
|
90
|
+
importInProgress: false,
|
|
91
|
+
|
|
82
92
|
// ── Editor state ──────────────────────────────────────────
|
|
83
93
|
editorFiles: {},
|
|
84
94
|
editorActiveFile: null,
|
|
@@ -132,6 +142,22 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
132
142
|
|| p.contextUsage !== a.contextUsage || p.name !== a.name || p.model !== a.model;
|
|
133
143
|
});
|
|
134
144
|
set({ agents: changed ? msg.data : prev, tokenTimeline: timeline, hydrated: true });
|
|
145
|
+
|
|
146
|
+
// Poll for recommended-team.json while a planner is running
|
|
147
|
+
const hasRunningPlanner = msg.data.some((a) => a.role === 'planner' && a.status === 'running');
|
|
148
|
+
if (hasRunningPlanner && !plannerPollInterval && !get().recommendedTeam) {
|
|
149
|
+
plannerPollInterval = setInterval(() => {
|
|
150
|
+
if (get().recommendedTeam) {
|
|
151
|
+
clearInterval(plannerPollInterval);
|
|
152
|
+
plannerPollInterval = null;
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
get().checkRecommendedTeam();
|
|
156
|
+
}, 3000);
|
|
157
|
+
} else if ((!hasRunningPlanner || get().recommendedTeam) && plannerPollInterval) {
|
|
158
|
+
clearInterval(plannerPollInterval);
|
|
159
|
+
plannerPollInterval = null;
|
|
160
|
+
}
|
|
135
161
|
break;
|
|
136
162
|
}
|
|
137
163
|
|
|
@@ -320,7 +346,6 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
320
346
|
|
|
321
347
|
case 'approval:request':
|
|
322
348
|
set((s) => ({ pendingApprovals: [...s.pendingApprovals, msg.data] }));
|
|
323
|
-
get().addToast('warning', `Approval needed: ${msg.data?.agentName || 'agent'}`, msg.data?.action?.description);
|
|
324
349
|
break;
|
|
325
350
|
|
|
326
351
|
case 'approval:resolved': {
|
|
@@ -351,10 +376,32 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
351
376
|
case 'gateway:status':
|
|
352
377
|
set({ gateways: msg.data || [] });
|
|
353
378
|
break;
|
|
379
|
+
|
|
380
|
+
case 'tunnel.connected':
|
|
381
|
+
set({ activeTunnelId: msg.data?.id || null });
|
|
382
|
+
get().fetchTunnels();
|
|
383
|
+
break;
|
|
384
|
+
|
|
385
|
+
case 'tunnel.disconnected':
|
|
386
|
+
set({ activeTunnelId: null });
|
|
387
|
+
get().fetchTunnels();
|
|
388
|
+
break;
|
|
389
|
+
|
|
390
|
+
case 'tunnel.health': {
|
|
391
|
+
const tunnels = get().savedTunnels.map((t) =>
|
|
392
|
+
t.id === msg.data?.id ? { ...t, latencyMs: msg.data.latencyMs, healthy: msg.data.healthy } : t,
|
|
393
|
+
);
|
|
394
|
+
set({ savedTunnels: tunnels });
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
354
397
|
}
|
|
355
398
|
};
|
|
356
399
|
|
|
357
400
|
ws.onclose = () => {
|
|
401
|
+
if (plannerPollInterval) {
|
|
402
|
+
clearInterval(plannerPollInterval);
|
|
403
|
+
plannerPollInterval = null;
|
|
404
|
+
}
|
|
358
405
|
set({ connected: false, hydrated: false, ws: null, daemonHost: null, tunneled: false });
|
|
359
406
|
setTimeout(() => get().connect(), 2000);
|
|
360
407
|
};
|
|
@@ -462,6 +509,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
462
509
|
set((s) => ({ detailPanel: null, teamDetailPanels: { ...s.teamDetailPanels, [tid]: null } }));
|
|
463
510
|
},
|
|
464
511
|
toggleCommandPalette() { set((s) => ({ commandPaletteOpen: !s.commandPaletteOpen })); },
|
|
512
|
+
toggleQuickConnect() { set((s) => ({ quickConnectOpen: !s.quickConnectOpen })); },
|
|
465
513
|
|
|
466
514
|
setDetailPanelWidth(w) {
|
|
467
515
|
set({ detailPanelWidth: w });
|
|
@@ -596,9 +644,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
596
644
|
|
|
597
645
|
// Check if all recommended roles already exist in the planner's team.
|
|
598
646
|
// If so, auto-delegate instead of showing the "Launch Team" modal.
|
|
599
|
-
const
|
|
600
|
-
const planner = planners.sort((a, b) => (b.lastActivity || '').localeCompare(a.lastActivity || ''))[0];
|
|
601
|
-
const teamId = planner?.teamId;
|
|
647
|
+
const teamId = data.teamId || null;
|
|
602
648
|
|
|
603
649
|
if (teamId) {
|
|
604
650
|
const teamAgents = get().agents.filter((a) => a.teamId === teamId && a.role !== 'planner');
|
|
@@ -624,7 +670,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
624
670
|
}
|
|
625
671
|
|
|
626
672
|
// New agents needed — show the modal for approval
|
|
627
|
-
set({ recommendedTeam: data });
|
|
673
|
+
set({ recommendedTeam: { ...data, teamId: data.teamId || null } });
|
|
628
674
|
} catch {
|
|
629
675
|
set({ recommendedTeam: null });
|
|
630
676
|
}
|
|
@@ -632,9 +678,10 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
632
678
|
|
|
633
679
|
async launchRecommendedTeam(modifiedAgents) {
|
|
634
680
|
try {
|
|
681
|
+
const teamId = get().recommendedTeam?.teamId || null;
|
|
635
682
|
set({ recommendedTeam: null }); // Dismiss modal immediately
|
|
636
683
|
get().addToast('info', 'Launching team...');
|
|
637
|
-
const body = modifiedAgents
|
|
684
|
+
const body = { ...(modifiedAgents && { agents: modifiedAgents }), ...(teamId && { teamId }) };
|
|
638
685
|
const result = await api.post('/recommended-team/launch', body);
|
|
639
686
|
const sub = [
|
|
640
687
|
result.phase2Pending ? `${result.phase2Pending} QC queued` : '',
|
|
@@ -657,6 +704,92 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
657
704
|
}
|
|
658
705
|
},
|
|
659
706
|
|
|
707
|
+
// ── GitHub Repo Import ────────────────────────────────────
|
|
708
|
+
|
|
709
|
+
async fetchImportedRepos() {
|
|
710
|
+
try {
|
|
711
|
+
const repos = await api.get('/repos/imported');
|
|
712
|
+
set({ importedRepos: repos });
|
|
713
|
+
} catch { /* ignore */ }
|
|
714
|
+
},
|
|
715
|
+
|
|
716
|
+
async previewRepo(repoUrl) {
|
|
717
|
+
return api.post('/repos/preview', { repoUrl });
|
|
718
|
+
},
|
|
719
|
+
|
|
720
|
+
async importRepo(repoUrl, targetPath, createTeam, teamName) {
|
|
721
|
+
set({ importInProgress: true });
|
|
722
|
+
try {
|
|
723
|
+
const result = await api.post('/repos/import', { repoUrl, targetPath, createTeam, teamName });
|
|
724
|
+
get().fetchImportedRepos();
|
|
725
|
+
return result;
|
|
726
|
+
} finally {
|
|
727
|
+
set({ importInProgress: false });
|
|
728
|
+
}
|
|
729
|
+
},
|
|
730
|
+
|
|
731
|
+
async softRemoveRepo(importId) {
|
|
732
|
+
await api.delete('/repos/' + importId + '/remove');
|
|
733
|
+
get().fetchImportedRepos();
|
|
734
|
+
},
|
|
735
|
+
|
|
736
|
+
async hardNukeRepo(importId, deleteFiles = true) {
|
|
737
|
+
await api.delete('/repos/' + importId + '/nuke?deleteFiles=' + deleteFiles);
|
|
738
|
+
get().fetchImportedRepos();
|
|
739
|
+
},
|
|
740
|
+
|
|
741
|
+
// ── Tunnels ──────────────────────────────────────────────
|
|
742
|
+
|
|
743
|
+
async fetchTunnels() {
|
|
744
|
+
try {
|
|
745
|
+
const tunnels = await api.get('/tunnels');
|
|
746
|
+
set({ savedTunnels: Array.isArray(tunnels) ? tunnels : [] });
|
|
747
|
+
} catch {}
|
|
748
|
+
},
|
|
749
|
+
|
|
750
|
+
async saveTunnel(config) {
|
|
751
|
+
const result = await api.post('/tunnels', config);
|
|
752
|
+
get().fetchTunnels();
|
|
753
|
+
return result;
|
|
754
|
+
},
|
|
755
|
+
|
|
756
|
+
async updateTunnel(id, config) {
|
|
757
|
+
const result = await api.patch('/tunnels/' + id, config);
|
|
758
|
+
get().fetchTunnels();
|
|
759
|
+
return result;
|
|
760
|
+
},
|
|
761
|
+
|
|
762
|
+
async deleteTunnel(id) {
|
|
763
|
+
await api.delete('/tunnels/' + id);
|
|
764
|
+
get().fetchTunnels();
|
|
765
|
+
},
|
|
766
|
+
|
|
767
|
+
async testTunnel(id) {
|
|
768
|
+
return api.post('/tunnels/' + id + '/test');
|
|
769
|
+
},
|
|
770
|
+
|
|
771
|
+
async connectTunnel(id) {
|
|
772
|
+
const result = await api.post('/tunnels/' + id + '/connect');
|
|
773
|
+
set({ activeTunnelId: id });
|
|
774
|
+
get().fetchTunnels();
|
|
775
|
+
if (result.url) window.open(result.url, '_blank');
|
|
776
|
+
return result;
|
|
777
|
+
},
|
|
778
|
+
|
|
779
|
+
async disconnectTunnel(id) {
|
|
780
|
+
await api.post('/tunnels/' + id + '/disconnect');
|
|
781
|
+
set({ activeTunnelId: null });
|
|
782
|
+
get().fetchTunnels();
|
|
783
|
+
},
|
|
784
|
+
|
|
785
|
+
async installTunnel(id) {
|
|
786
|
+
return api.post('/tunnels/' + id + '/install');
|
|
787
|
+
},
|
|
788
|
+
|
|
789
|
+
async startTunnel(id) {
|
|
790
|
+
return api.post('/tunnels/' + id + '/start');
|
|
791
|
+
},
|
|
792
|
+
|
|
660
793
|
// ── Journalist ────────────────────────────────────────────
|
|
661
794
|
|
|
662
795
|
async fetchJournalist() {
|
|
@@ -112,54 +112,53 @@ export default function DashboardView() {
|
|
|
112
112
|
|
|
113
113
|
<KpiStrip kpis={kpis} />
|
|
114
114
|
|
|
115
|
-
<div className="flex-1 min-h-0
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}}>
|
|
121
|
-
<div className="min-w-0 min-h-0 overflow-hidden bg-surface-1 relative">
|
|
122
|
-
<TokenChart data={snapshots} />
|
|
123
|
-
</div>
|
|
115
|
+
<div className="flex-1 min-h-0 flex flex-col" style={{ background: '#282c34', gap: '1px' }}>
|
|
116
|
+
<div className="min-h-0 flex-1 grid" style={{ gridTemplateColumns: '3fr 1.5fr 1.5fr', gap: '0 1px' }}>
|
|
117
|
+
<div className="min-w-0 min-h-0 overflow-hidden bg-surface-1 relative">
|
|
118
|
+
<TokenChart data={snapshots} />
|
|
119
|
+
</div>
|
|
124
120
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
<div className="min-w-0 min-h-0 overflow-hidden bg-surface-1 flex flex-col border-l border-border">
|
|
122
|
+
<div className="px-3 pt-2.5 pb-1">
|
|
123
|
+
<span className="text-2xs font-mono text-text-3 uppercase tracking-widest">Cache Performance</span>
|
|
124
|
+
</div>
|
|
125
|
+
<CacheRing
|
|
126
|
+
cacheRead={tokens.cacheReadTokens}
|
|
127
|
+
cacheCreation={tokens.cacheCreationTokens}
|
|
128
|
+
totalInput={tokens.totalInputTokens}
|
|
129
|
+
/>
|
|
128
130
|
</div>
|
|
129
|
-
<CacheRing
|
|
130
|
-
cacheRead={tokens.cacheReadTokens}
|
|
131
|
-
cacheCreation={tokens.cacheCreationTokens}
|
|
132
|
-
totalInput={tokens.totalInputTokens}
|
|
133
|
-
/>
|
|
134
|
-
</div>
|
|
135
131
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
132
|
+
<div className="min-w-0 min-h-0 overflow-hidden bg-surface-1 flex flex-col border-l border-border">
|
|
133
|
+
<div className="px-3 pt-2.5 pb-1">
|
|
134
|
+
<span className="text-2xs font-mono text-text-3 uppercase tracking-widest">Model Routing</span>
|
|
135
|
+
</div>
|
|
136
|
+
<RoutingChart routing={routing} agentBreakdown={agentBreakdown} />
|
|
139
137
|
</div>
|
|
140
|
-
<RoutingChart routing={routing} agentBreakdown={agentBreakdown} />
|
|
141
138
|
</div>
|
|
142
139
|
|
|
143
|
-
<div className="min-
|
|
144
|
-
<div className="
|
|
145
|
-
<
|
|
140
|
+
<div className="min-h-0 flex-1 grid" style={{ gridTemplateColumns: '2fr 2.5fr 1.5fr', gap: '0 1px' }}>
|
|
141
|
+
<div className="min-w-0 min-h-0 overflow-hidden bg-surface-1 flex flex-col border-t border-border">
|
|
142
|
+
<div className="px-3 pt-2.5 pb-1 flex-shrink-0">
|
|
143
|
+
<span className="text-2xs font-mono text-text-3 uppercase tracking-widest">Agent Fleet</span>
|
|
144
|
+
</div>
|
|
145
|
+
<FleetPanel agentBreakdown={agentBreakdown} rotating={rotating} teams={teams} />
|
|
146
146
|
</div>
|
|
147
|
-
<FleetPanel agentBreakdown={agentBreakdown} rotating={rotating} teams={teams} />
|
|
148
|
-
</div>
|
|
149
147
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
148
|
+
<div className="min-w-0 min-h-0 overflow-hidden bg-surface-1 flex flex-col border-t border-l border-border">
|
|
149
|
+
<IntelPanel
|
|
150
|
+
tokens={tokens}
|
|
151
|
+
rotation={rotation}
|
|
152
|
+
adaptive={adaptive}
|
|
153
|
+
journalist={journalist}
|
|
154
|
+
agentBreakdown={agentBreakdown}
|
|
155
|
+
memory={memory}
|
|
156
|
+
/>
|
|
157
|
+
</div>
|
|
160
158
|
|
|
161
|
-
|
|
162
|
-
|
|
159
|
+
<div className="min-w-0 min-h-0 overflow-hidden bg-surface-1 flex flex-col border-t border-l border-border">
|
|
160
|
+
<TeamBurnPanel teams={teamBurn} />
|
|
161
|
+
</div>
|
|
163
162
|
</div>
|
|
164
163
|
</div>
|
|
165
164
|
|