groove-dev 0.27.130 → 0.27.131
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/model-workspace/LAB-ASSISTANT-BUILD-PLAN.md +341 -0
- package/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/api.js +25 -0
- package/node_modules/@groove-dev/daemon/src/index.js +15 -3
- package/node_modules/@groove-dev/daemon/src/llama-server.js +1 -1
- package/node_modules/@groove-dev/daemon/src/process.js +6 -0
- package/node_modules/@groove-dev/daemon/src/state.js +18 -2
- package/node_modules/@groove-dev/daemon/src/teams.js +26 -7
- package/node_modules/@groove-dev/daemon/templates/tgi-setup.json +12 -0
- package/node_modules/@groove-dev/daemon/templates/vllm-setup.json +12 -0
- package/node_modules/@groove-dev/gui/dist/assets/{index-6zTBb9ik.js → index-BiB9oY9U.js} +558 -557
- package/node_modules/@groove-dev/gui/dist/assets/index-CeyDFVub.css +1 -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/lab/lab-assistant.jsx +199 -0
- package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +33 -10
- package/node_modules/@groove-dev/gui/src/stores/groove.js +35 -0
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +7 -1
- package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +31 -3
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/api.js +25 -0
- package/packages/daemon/src/index.js +15 -3
- package/packages/daemon/src/llama-server.js +1 -1
- package/packages/daemon/src/process.js +6 -0
- package/packages/daemon/src/state.js +18 -2
- package/packages/daemon/src/teams.js +26 -7
- package/packages/daemon/templates/tgi-setup.json +12 -0
- package/packages/daemon/templates/vllm-setup.json +12 -0
- package/packages/gui/dist/assets/{index-6zTBb9ik.js → index-BiB9oY9U.js} +558 -557
- package/packages/gui/dist/assets/index-CeyDFVub.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/lab/lab-assistant.jsx +199 -0
- package/packages/gui/src/components/lab/runtime-config.jsx +33 -10
- package/packages/gui/src/stores/groove.js +35 -0
- package/packages/gui/src/views/agents.jsx +7 -1
- package/packages/gui/src/views/model-lab.jsx +31 -3
- package/local-models/daemon-bridge.js +0 -87
- package/node_modules/@groove-dev/gui/dist/assets/index-Ch9Mlf9w.css +0 -1
- package/packages/gui/dist/assets/index-Ch9Mlf9w.css +0 -1
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
# Lab Assistant — Build Plan
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Add an embedded AI assistant to the Model Lab that helps users set up inference runtimes (vLLM, TGI) without leaving the Lab. One agent, one chat, inline in the center panel. The agent handles system recon, installation, configuration, server startup, and auto-creates the Lab runtime when done.
|
|
6
|
+
|
|
7
|
+
## User Flow
|
|
8
|
+
|
|
9
|
+
1. User selects a model + picks vLLM or TGI backend in the Launch Model section
|
|
10
|
+
2. Clicks **"Setup vLLM with Assistant"** button
|
|
11
|
+
3. Center panel switches from Playground to Assistant tab — agent chat appears
|
|
12
|
+
4. Agent: "Let me check your system..." → runs nvidia-smi, checks CUDA, Python, Docker, VRAM
|
|
13
|
+
5. Agent: "You have an RTX 4090 with 24GB VRAM. I'll set up vLLM via Docker with Qwen3-8B."
|
|
14
|
+
6. Agent installs, configures, starts the server, validates with a health check
|
|
15
|
+
7. Agent calls `POST http://localhost:31415/api/lab/runtimes` to register the runtime
|
|
16
|
+
8. Runtime appears in the Lab's left panel automatically (via WebSocket event)
|
|
17
|
+
9. Agent: "Done! Switch to the Playground tab to start chatting."
|
|
18
|
+
10. User clicks "Switch to Playground" — model is ready to use
|
|
19
|
+
|
|
20
|
+
## Architecture
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
User clicks "Setup vLLM"
|
|
24
|
+
→ store.launchLabAssistant('vllm')
|
|
25
|
+
→ POST /api/lab/assistant { backend: 'vllm' }
|
|
26
|
+
→ daemon reads templates/vllm-setup.json
|
|
27
|
+
→ daemon.processes.spawn({ role: 'lab-assistant', prompt: ... })
|
|
28
|
+
→ returns { agentId }
|
|
29
|
+
→ store sets labAssistantAgentId, switches to Assistant tab
|
|
30
|
+
→ agent output streams via WebSocket → chatHistory[agentId]
|
|
31
|
+
|
|
32
|
+
Agent runs system commands, installs vLLM, starts server
|
|
33
|
+
→ agent calls: curl POST /api/lab/runtimes (on localhost)
|
|
34
|
+
→ daemon creates runtime, broadcasts lab:runtime:added
|
|
35
|
+
→ store.fetchLabRuntimes() fires → left panel updates
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Implementation
|
|
39
|
+
|
|
40
|
+
### 1. Team Templates (new files)
|
|
41
|
+
|
|
42
|
+
**`packages/daemon/templates/vllm-setup.json`**
|
|
43
|
+
|
|
44
|
+
Single-agent template. Role is `lab-assistant` (avoids the `planner` role which is restricted to planning-only in `ROLE_PROMPTS`).
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"name": "vllm-setup",
|
|
49
|
+
"description": "Lab Assistant for vLLM installation and configuration",
|
|
50
|
+
"agents": [
|
|
51
|
+
{
|
|
52
|
+
"role": "lab-assistant",
|
|
53
|
+
"scope": [],
|
|
54
|
+
"provider": "claude-code",
|
|
55
|
+
"prompt": "SEE PROMPT SPEC BELOW"
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**`packages/daemon/templates/tgi-setup.json`** — same structure, TGI-specific prompt.
|
|
62
|
+
|
|
63
|
+
#### Planner Prompt Spec (vLLM)
|
|
64
|
+
|
|
65
|
+
The prompt is the hardest part. It must cover:
|
|
66
|
+
|
|
67
|
+
**Identity**: "You are a GROOVE Lab Assistant. You help the user set up a vLLM inference server. Be conversational, report progress clearly."
|
|
68
|
+
|
|
69
|
+
**System Recon** (run these commands, report findings):
|
|
70
|
+
- `nvidia-smi` — GPU model, VRAM, driver version
|
|
71
|
+
- `nvcc --version` — CUDA toolkit version
|
|
72
|
+
- `python3 --version` and `pip3 --version`
|
|
73
|
+
- `docker --version`
|
|
74
|
+
- `free -h` — available RAM
|
|
75
|
+
- `df -h /` — disk space
|
|
76
|
+
|
|
77
|
+
**Decision Matrix**:
|
|
78
|
+
- Docker available + NVIDIA GPU → Docker path (simplest)
|
|
79
|
+
- No Docker, Python 3.8+ and CUDA → pip path
|
|
80
|
+
- No GPU → warn user, suggest llama.cpp/Ollama instead
|
|
81
|
+
- VRAM sizing: <8GB → 1-3B models, 8-16GB → 7B, 16-24GB → 13B, 24-48GB → 30-70B quantized, 48GB+ → 70B+
|
|
82
|
+
|
|
83
|
+
**Installation**:
|
|
84
|
+
- Docker: `docker run --runtime nvidia --gpus all -v ~/.cache/huggingface:/root/.cache/huggingface -p 8000:8000 --ipc=host vllm/vllm-openai:latest --model <model>`
|
|
85
|
+
- Pip: `pip install vllm && vllm serve <model> --host 0.0.0.0 --port 8000`
|
|
86
|
+
- Must use `nohup` or `docker run -d` so server persists after agent exits
|
|
87
|
+
|
|
88
|
+
**Validation**: `curl http://localhost:8000/v1/models` — confirm JSON response
|
|
89
|
+
|
|
90
|
+
**Runtime Registration**:
|
|
91
|
+
```bash
|
|
92
|
+
PORT=$(cat ~/.groove/daemon.port 2>/dev/null || echo 31415)
|
|
93
|
+
curl -s -X POST http://localhost:$PORT/api/lab/runtimes \
|
|
94
|
+
-H 'Content-Type: application/json' \
|
|
95
|
+
-d '{"name":"vLLM - <model>","type":"vllm","endpoint":"http://localhost:8000"}'
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Completion**: Tell user to switch to Playground tab.
|
|
99
|
+
|
|
100
|
+
**Error Handling**: Explain errors clearly, suggest fixes, offer retry. Common issues: CUDA mismatch, insufficient VRAM, Docker not running, missing nvidia-container-toolkit.
|
|
101
|
+
|
|
102
|
+
#### TGI Prompt Spec
|
|
103
|
+
|
|
104
|
+
Same structure, but:
|
|
105
|
+
- Docker: `ghcr.io/huggingface/text-generation-inference`
|
|
106
|
+
- Default port: 8080
|
|
107
|
+
- Model loading with `--model-id`
|
|
108
|
+
- Runtime type: `"tgi"`
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
### 2. Daemon Endpoint
|
|
113
|
+
|
|
114
|
+
**File**: `packages/daemon/src/api.js`
|
|
115
|
+
**Location**: After the existing lab endpoints (after `GET /api/lab/sessions/:id`, around line 6760)
|
|
116
|
+
|
|
117
|
+
**`POST /api/lab/assistant`**
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
Request: { "backend": "vllm" | "tgi" }
|
|
121
|
+
Response: { "agentId": "abc123", "backend": "vllm" }
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Implementation:
|
|
125
|
+
1. Validate `backend` is "vllm" or "tgi"
|
|
126
|
+
2. Read template: `readFileSync(resolve(__dirname, '../templates/${backend}-setup.json'), 'utf8')`
|
|
127
|
+
3. Parse JSON, extract the agent config (first entry in `agents` array)
|
|
128
|
+
4. Spawn agent via `daemon.processes.spawn()` with:
|
|
129
|
+
- `role`: "lab-assistant"
|
|
130
|
+
- `provider`: agent config provider or daemon default
|
|
131
|
+
- `prompt`: agent config prompt
|
|
132
|
+
- `teamId`: default team ID
|
|
133
|
+
- `metadata`: `{ labAssistant: true, backend }`
|
|
134
|
+
5. Return `201` with `{ agentId: agent.id, backend }`
|
|
135
|
+
|
|
136
|
+
**Import needed**: `readFileSync` from `fs`, `resolve` from `path` (likely already imported in api.js)
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
### 3. Store State & Actions
|
|
141
|
+
|
|
142
|
+
**File**: `packages/gui/src/stores/groove.js`
|
|
143
|
+
|
|
144
|
+
**New state** (add after `labLaunchError`):
|
|
145
|
+
```js
|
|
146
|
+
labAssistantAgentId: null,
|
|
147
|
+
labAssistantMode: false,
|
|
148
|
+
labAssistantBackend: null,
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**New actions** (add after the existing lab actions block):
|
|
152
|
+
|
|
153
|
+
```js
|
|
154
|
+
async launchLabAssistant(backend) {
|
|
155
|
+
const existing = get().labAssistantAgentId;
|
|
156
|
+
if (existing) {
|
|
157
|
+
// If assistant already running, just switch to its tab
|
|
158
|
+
const agent = get().agents.find((a) => a.id === existing);
|
|
159
|
+
if (agent && agent.status === 'running') {
|
|
160
|
+
set({ labAssistantMode: true });
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
const data = await api.post('/lab/assistant', { backend });
|
|
166
|
+
set({ labAssistantAgentId: data.agentId, labAssistantMode: true, labAssistantBackend: backend });
|
|
167
|
+
get().addToast('info', `Lab Assistant started for ${backend}`);
|
|
168
|
+
} catch (err) {
|
|
169
|
+
get().addToast('error', 'Failed to start assistant', err.message);
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
dismissLabAssistant() {
|
|
174
|
+
set({ labAssistantMode: false });
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
clearLabAssistant() {
|
|
178
|
+
const id = get().labAssistantAgentId;
|
|
179
|
+
if (id) api.delete(`/agents/${id}`).catch(() => {});
|
|
180
|
+
set({ labAssistantAgentId: null, labAssistantMode: false, labAssistantBackend: null });
|
|
181
|
+
},
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**No WebSocket changes needed** — `agent:output` already populates `chatHistory[agentId]`, and `lab:runtime:added` already triggers `fetchLabRuntimes()`.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
### 4. Lab Assistant Component (new file)
|
|
189
|
+
|
|
190
|
+
**File**: `packages/gui/src/components/lab/lab-assistant.jsx`
|
|
191
|
+
|
|
192
|
+
A focused chat component for the lab assistant agent. Reads from `chatHistory[labAssistantAgentId]`, sends via the existing agent instruction mechanism.
|
|
193
|
+
|
|
194
|
+
**Structure**:
|
|
195
|
+
```
|
|
196
|
+
LabAssistant
|
|
197
|
+
├── Header: backend badge, agent status, dismiss "X" button
|
|
198
|
+
├── ScrollArea: message list
|
|
199
|
+
│ ├── Agent messages (left-aligned, border-l accent)
|
|
200
|
+
│ └── User messages (right-aligned, accent/10 bg)
|
|
201
|
+
├── Thinking indicator (when agent is processing)
|
|
202
|
+
├── Completion banner: "Setup complete — Switch to Playground" button
|
|
203
|
+
└── Input: textarea + send button (Enter to send, Shift+Enter newline)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Key store selectors**:
|
|
207
|
+
- `labAssistantAgentId` — which agent to display
|
|
208
|
+
- `chatHistory[agentId]` — message history
|
|
209
|
+
- `agents.find(a => a.id === agentId)` — agent status (running/completed/error)
|
|
210
|
+
- `dismissLabAssistant` — switch back to playground
|
|
211
|
+
|
|
212
|
+
**Sending messages**: Use the existing `instructAgent(agentId, message)` store action (check if this exists — it should be the mechanism used by AgentChat). If not, use `api.post('/agents/${id}/instruct', { message })`.
|
|
213
|
+
|
|
214
|
+
**Styling**: Match `chat-playground.jsx` patterns exactly — same message bubble styles, same input area, same auto-scroll logic. License header on line 1.
|
|
215
|
+
|
|
216
|
+
**Completion detection**: When `agent.status !== 'running'` and the last message exists, show the "Switch to Playground" button.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
### 5. Center Panel Mode Switch
|
|
221
|
+
|
|
222
|
+
**File**: `packages/gui/src/views/model-lab.jsx`
|
|
223
|
+
|
|
224
|
+
**New import**: `import { LabAssistant } from '../components/lab/lab-assistant';`
|
|
225
|
+
|
|
226
|
+
**New store selectors** in `ModelLabView`:
|
|
227
|
+
```js
|
|
228
|
+
const labAssistantAgentId = useGrooveStore((s) => s.labAssistantAgentId);
|
|
229
|
+
const labAssistantMode = useGrooveStore((s) => s.labAssistantMode);
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**Replace the center panel** (currently `<div className="flex-1 min-w-0 p-3"><ChatPlayground /></div>`) with:
|
|
233
|
+
|
|
234
|
+
```jsx
|
|
235
|
+
<div className="flex-1 min-w-0 flex flex-col">
|
|
236
|
+
{/* Tab bar — only visible when assistant exists */}
|
|
237
|
+
{labAssistantAgentId && (
|
|
238
|
+
<div className="flex-shrink-0 flex items-center gap-1 px-3 pt-2">
|
|
239
|
+
<button
|
|
240
|
+
onClick={() => set({ labAssistantMode: false })}
|
|
241
|
+
className={cn(
|
|
242
|
+
'px-3 py-1.5 text-xs font-sans font-medium rounded-t-md transition-colors cursor-pointer',
|
|
243
|
+
!labAssistantMode ? 'text-accent bg-accent/10' : 'text-text-3 hover:text-text-1',
|
|
244
|
+
)}
|
|
245
|
+
>
|
|
246
|
+
Playground
|
|
247
|
+
</button>
|
|
248
|
+
<button
|
|
249
|
+
onClick={() => set({ labAssistantMode: true })}
|
|
250
|
+
className={cn(
|
|
251
|
+
'px-3 py-1.5 text-xs font-sans font-medium rounded-t-md transition-colors cursor-pointer',
|
|
252
|
+
labAssistantMode ? 'text-accent bg-accent/10' : 'text-text-3 hover:text-text-1',
|
|
253
|
+
)}
|
|
254
|
+
>
|
|
255
|
+
Assistant
|
|
256
|
+
</button>
|
|
257
|
+
</div>
|
|
258
|
+
)}
|
|
259
|
+
<div className="flex-1 min-h-0 p-3">
|
|
260
|
+
{labAssistantMode && labAssistantAgentId ? <LabAssistant /> : <ChatPlayground />}
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Note: The tab buttons need to call store's `set` function — either expose `setLabAssistantMode(bool)` as a store action, or use `dismissLabAssistant()` and `set({ labAssistantMode: true })`. Cleanest approach: add a `setLabAssistantMode(mode)` action to the store.
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
### 6. LaunchModel Button
|
|
270
|
+
|
|
271
|
+
**File**: `packages/gui/src/components/lab/runtime-config.jsx`
|
|
272
|
+
|
|
273
|
+
**In the `LaunchModel` component**, replace the static guidance box for vLLM/TGI:
|
|
274
|
+
|
|
275
|
+
Change from:
|
|
276
|
+
```jsx
|
|
277
|
+
{!currentBackend?.autoLaunch && (
|
|
278
|
+
<div>...manual setup guidance...</div>
|
|
279
|
+
)}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
To:
|
|
283
|
+
```jsx
|
|
284
|
+
{!currentBackend?.autoLaunch && (
|
|
285
|
+
<div className="space-y-2">
|
|
286
|
+
<Button variant="primary" size="sm" className="w-full" onClick={handleLaunchAssistant} disabled={assistantLaunching}>
|
|
287
|
+
{assistantLaunching
|
|
288
|
+
? <><Loader2 size={12} className="animate-spin mr-1.5" /> Starting Assistant...</>
|
|
289
|
+
: <><Wrench size={12} className="mr-1.5" /> Setup {currentBackend?.label} with Assistant</>
|
|
290
|
+
}
|
|
291
|
+
</Button>
|
|
292
|
+
<p className="text-2xs text-text-4 font-sans px-1">
|
|
293
|
+
An AI assistant will check your system and handle the installation, or start your server manually and add it as a Runtime below.
|
|
294
|
+
</p>
|
|
295
|
+
</div>
|
|
296
|
+
)}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**New imports**: `Wrench` from lucide-react, `launchLabAssistant` from store.
|
|
300
|
+
|
|
301
|
+
**Update BACKENDS array** subtitle for vLLM/TGI:
|
|
302
|
+
```js
|
|
303
|
+
{ id: 'vllm', label: 'vLLM', subtitle: 'GPU-optimized, guided setup', autoLaunch: false },
|
|
304
|
+
{ id: 'tgi', label: 'TGI', subtitle: 'HuggingFace, guided setup', autoLaunch: false },
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## File Summary
|
|
310
|
+
|
|
311
|
+
| File | Action | Description |
|
|
312
|
+
|------|--------|-------------|
|
|
313
|
+
| `packages/daemon/templates/vllm-setup.json` | CREATE | vLLM setup agent template + prompt |
|
|
314
|
+
| `packages/daemon/templates/tgi-setup.json` | CREATE | TGI setup agent template + prompt |
|
|
315
|
+
| `packages/daemon/src/api.js` | MODIFY | Add `POST /api/lab/assistant` endpoint |
|
|
316
|
+
| `packages/gui/src/stores/groove.js` | MODIFY | Add labAssistant state + actions |
|
|
317
|
+
| `packages/gui/src/components/lab/lab-assistant.jsx` | CREATE | Assistant chat component |
|
|
318
|
+
| `packages/gui/src/views/model-lab.jsx` | MODIFY | Center panel tab switch |
|
|
319
|
+
| `packages/gui/src/components/lab/runtime-config.jsx` | MODIFY | "Setup with Assistant" button |
|
|
320
|
+
|
|
321
|
+
## Build Order
|
|
322
|
+
|
|
323
|
+
1. Team templates (`vllm-setup.json`, `tgi-setup.json`)
|
|
324
|
+
2. Daemon endpoint (`POST /api/lab/assistant` in `api.js`)
|
|
325
|
+
3. Store state and actions (`groove.js`)
|
|
326
|
+
4. Lab Assistant component (`lab-assistant.jsx`)
|
|
327
|
+
5. Center panel mode switch (`model-lab.jsx`)
|
|
328
|
+
6. LaunchModel button (`runtime-config.jsx`)
|
|
329
|
+
7. `npm run build` from `packages/gui/`
|
|
330
|
+
8. End-to-end test
|
|
331
|
+
|
|
332
|
+
## Key Constraints
|
|
333
|
+
|
|
334
|
+
- **Role must be `lab-assistant`** — not `planner` (which is restricted to planning-only in `ROLE_PROMPTS`)
|
|
335
|
+
- **Agent sends messages via WebSocket** — the `chatHistory[agentId]` pattern already handles this
|
|
336
|
+
- **Runtime creation via curl** — the agent calls the daemon REST API directly. The daemon is on localhost:31415 (or read from `~/.groove/daemon.port`)
|
|
337
|
+
- **Server must persist** — use `nohup`, `docker run -d`, or background processes so the inference server outlives the agent
|
|
338
|
+
- **No inline styles** — Tailwind CSS only
|
|
339
|
+
- **License header** — `// FSL-1.1-Apache-2.0 — see LICENSE` on every new source file
|
|
340
|
+
- **ESM imports** — all files use `import`/`export`
|
|
341
|
+
- **Do NOT restart the daemon** — verify with `npm run build` only
|
|
@@ -6764,6 +6764,31 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
6764
6764
|
res.json(session);
|
|
6765
6765
|
});
|
|
6766
6766
|
|
|
6767
|
+
app.post('/api/lab/assistant', async (req, res) => {
|
|
6768
|
+
try {
|
|
6769
|
+
const { backend } = req.body || {};
|
|
6770
|
+
if (!backend || !['vllm', 'tgi'].includes(backend)) {
|
|
6771
|
+
return res.status(400).json({ error: 'backend must be "vllm" or "tgi"' });
|
|
6772
|
+
}
|
|
6773
|
+
const templatePath = resolve(__dirname, `../templates/${backend}-setup.json`);
|
|
6774
|
+
const template = JSON.parse(readFileSync(templatePath, 'utf8'));
|
|
6775
|
+
const agentConfig = template.agents[0];
|
|
6776
|
+
const config = {
|
|
6777
|
+
role: 'lab-assistant',
|
|
6778
|
+
scope: agentConfig.scope || [],
|
|
6779
|
+
provider: agentConfig.provider || daemon.config.defaultProvider,
|
|
6780
|
+
prompt: agentConfig.prompt,
|
|
6781
|
+
metadata: { labAssistant: true, backend },
|
|
6782
|
+
};
|
|
6783
|
+
if (!config.provider) config.provider = daemon.config.defaultProvider;
|
|
6784
|
+
const agent = await daemon.processes.spawn(config);
|
|
6785
|
+
daemon.audit.log('lab.assistant.spawn', { id: agent.id, backend });
|
|
6786
|
+
res.status(201).json({ agentId: agent.id, backend });
|
|
6787
|
+
} catch (err) {
|
|
6788
|
+
res.status(400).json({ error: err.message });
|
|
6789
|
+
}
|
|
6790
|
+
});
|
|
6791
|
+
|
|
6767
6792
|
// --- Wallet & earnings stubs (Base L2 — wired to real data post-mainnet) ---
|
|
6768
6793
|
|
|
6769
6794
|
app.get('/api/network/wallet', networkGate, (req, res) => {
|
|
@@ -719,10 +719,23 @@ export class Daemon {
|
|
|
719
719
|
|
|
720
720
|
try {
|
|
721
721
|
// Build set of agent names still in the registry — never remove their logs
|
|
722
|
-
const
|
|
722
|
+
const allAgents = this.registry.getAll();
|
|
723
|
+
const activeNames = new Set(allAgents.map((a) => a.name));
|
|
723
724
|
|
|
724
|
-
//
|
|
725
|
+
// Safety: if registry is empty but log files exist, state may have been
|
|
726
|
+
// lost (corrupt JSON, partial write). Skip log cleanup to prevent
|
|
727
|
+
// destroying agent history that could still be recovered.
|
|
725
728
|
const logsDir = resolve(grooveDir, 'logs');
|
|
729
|
+
const agentLogsDir = resolve(this.projectDir, 'GROOVE_AGENT_LOGS');
|
|
730
|
+
const hasOrphanedLogs = (existsSync(logsDir) && readdirSync(logsDir).length > 0) ||
|
|
731
|
+
(existsSync(agentLogsDir) && readdirSync(agentLogsDir).length > 0);
|
|
732
|
+
|
|
733
|
+
if (allAgents.length === 0 && hasOrphanedLogs) {
|
|
734
|
+
console.log('[Groove:GC] Registry empty but log files exist — skipping cleanup to prevent data loss');
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// 1. Clean raw log files for agents no longer in the registry
|
|
726
739
|
if (existsSync(logsDir)) {
|
|
727
740
|
for (const file of readdirSync(logsDir)) {
|
|
728
741
|
const agentName = file.replace(/\.log$/, '');
|
|
@@ -732,7 +745,6 @@ export class Daemon {
|
|
|
732
745
|
}
|
|
733
746
|
|
|
734
747
|
// 2. Clean GROOVE_AGENT_LOGS/ for agents no longer in the registry
|
|
735
|
-
const agentLogsDir = resolve(this.projectDir, 'GROOVE_AGENT_LOGS');
|
|
736
748
|
if (existsSync(agentLogsDir)) {
|
|
737
749
|
for (const dir of readdirSync(agentLogsDir, { withFileTypes: true })) {
|
|
738
750
|
if (!dir.isDirectory()) continue;
|
|
@@ -71,7 +71,7 @@ export class LlamaServerManager {
|
|
|
71
71
|
|
|
72
72
|
// Flash attention for better memory efficiency (if supported)
|
|
73
73
|
if (options.flashAttention !== false) {
|
|
74
|
-
args.push('--flash-attn');
|
|
74
|
+
args.push('--flash-attn', 'auto');
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
const proc = spawn('llama-server', args, {
|
|
@@ -1953,6 +1953,9 @@ For normal file edits within your scope, proceed without review.
|
|
|
1953
1953
|
this.daemon.trajectoryCapture.onAgentSpawn(
|
|
1954
1954
|
newAgent.id, config.provider, config.model || null, config.role, teamSize, config.prompt
|
|
1955
1955
|
).catch(() => {});
|
|
1956
|
+
if (message && typeof message === 'string' && message.trim()) {
|
|
1957
|
+
this.daemon.trajectoryCapture.onUserMessage(newAgent.id, message, 'user');
|
|
1958
|
+
}
|
|
1956
1959
|
} catch (e) { /* fail silent */ }
|
|
1957
1960
|
}
|
|
1958
1961
|
|
|
@@ -2095,6 +2098,9 @@ For normal file edits within your scope, proceed without review.
|
|
|
2095
2098
|
this.daemon.trajectoryCapture.onAgentSpawn(
|
|
2096
2099
|
newAgent.id, config.provider, loopConfig.model || config.model || null, config.role, teamSize, config.prompt
|
|
2097
2100
|
).catch(() => {});
|
|
2101
|
+
if (message && typeof message === 'string' && message.trim()) {
|
|
2102
|
+
this.daemon.trajectoryCapture.onUserMessage(newAgent.id, message, 'user');
|
|
2103
|
+
}
|
|
2098
2104
|
} catch (e) { /* fail silent */ }
|
|
2099
2105
|
}
|
|
2100
2106
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// GROOVE — State Persistence
|
|
2
2
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
3
3
|
|
|
4
|
-
import { readFileSync, existsSync, readdirSync, unlinkSync } from 'fs';
|
|
4
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, unlinkSync, renameSync, copyFileSync } from 'fs';
|
|
5
5
|
import { writeFile } from 'node:fs/promises';
|
|
6
6
|
import { resolve } from 'path';
|
|
7
7
|
|
|
@@ -9,6 +9,7 @@ export class StateManager {
|
|
|
9
9
|
constructor(grooveDir) {
|
|
10
10
|
this.grooveDir = grooveDir;
|
|
11
11
|
this.path = resolve(grooveDir, 'state.json');
|
|
12
|
+
this.backupPath = resolve(grooveDir, 'state.json.bak');
|
|
12
13
|
this.data = {};
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -16,13 +17,28 @@ export class StateManager {
|
|
|
16
17
|
if (existsSync(this.path)) {
|
|
17
18
|
try {
|
|
18
19
|
this.data = JSON.parse(readFileSync(this.path, 'utf8'));
|
|
20
|
+
return;
|
|
19
21
|
} catch {
|
|
20
|
-
|
|
22
|
+
console.error('[Groove:State] state.json corrupt — trying backup');
|
|
21
23
|
}
|
|
22
24
|
}
|
|
25
|
+
if (existsSync(this.backupPath)) {
|
|
26
|
+
try {
|
|
27
|
+
this.data = JSON.parse(readFileSync(this.backupPath, 'utf8'));
|
|
28
|
+
writeFileSync(this.path, readFileSync(this.backupPath, 'utf8'));
|
|
29
|
+
console.log('[Groove:State] Restored from state.json.bak');
|
|
30
|
+
return;
|
|
31
|
+
} catch {
|
|
32
|
+
console.error('[Groove:State] Backup also corrupt — starting fresh');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
this.data = {};
|
|
23
36
|
}
|
|
24
37
|
|
|
25
38
|
async save() {
|
|
39
|
+
if (existsSync(this.path)) {
|
|
40
|
+
try { copyFileSync(this.path, this.backupPath); } catch { /* non-fatal */ }
|
|
41
|
+
}
|
|
26
42
|
await writeFile(this.path, JSON.stringify(this.data, null, 2));
|
|
27
43
|
}
|
|
28
44
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// GROOVE — Teams (Live Agent Groups)
|
|
2
2
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
3
3
|
|
|
4
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync, renameSync, rmSync, readdirSync, cpSync } from 'fs';
|
|
4
|
+
import { readFileSync, writeFileSync, copyFileSync, existsSync, mkdirSync, renameSync, rmSync, readdirSync, cpSync } from 'fs';
|
|
5
5
|
import { resolve } from 'path';
|
|
6
6
|
import { randomUUID } from 'crypto';
|
|
7
7
|
import { validateTeamName, validateTeamMode } from './validate.js';
|
|
@@ -14,22 +14,41 @@ export class Teams {
|
|
|
14
14
|
constructor(daemon) {
|
|
15
15
|
this.daemon = daemon;
|
|
16
16
|
this.filePath = resolve(daemon.grooveDir, 'teams.json');
|
|
17
|
+
this.backupPath = resolve(daemon.grooveDir, 'teams.json.bak');
|
|
17
18
|
this.teams = new Map();
|
|
18
19
|
this._load();
|
|
19
20
|
this._ensureDefault();
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
_load() {
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
if (existsSync(this.filePath)) {
|
|
25
|
+
try {
|
|
26
|
+
const data = JSON.parse(readFileSync(this.filePath, 'utf8'));
|
|
27
|
+
if (Array.isArray(data)) {
|
|
28
|
+
for (const team of data) this.teams.set(team.id, team);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
console.error('[Groove:Teams] teams.json corrupt — trying backup');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (existsSync(this.backupPath)) {
|
|
36
|
+
try {
|
|
37
|
+
const data = JSON.parse(readFileSync(this.backupPath, 'utf8'));
|
|
38
|
+
if (Array.isArray(data)) {
|
|
39
|
+
for (const team of data) this.teams.set(team.id, team);
|
|
40
|
+
writeFileSync(this.filePath, readFileSync(this.backupPath, 'utf8'));
|
|
41
|
+
console.log('[Groove:Teams] Restored from teams.json.bak');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
console.error('[Groove:Teams] Backup also corrupt');
|
|
28
46
|
}
|
|
29
|
-
}
|
|
47
|
+
}
|
|
30
48
|
}
|
|
31
49
|
|
|
32
50
|
_save() {
|
|
51
|
+
try { copyFileSync(this.filePath, this.backupPath); } catch { /* may not exist yet */ }
|
|
33
52
|
writeFileSync(this.filePath, JSON.stringify([...this.teams.values()], null, 2));
|
|
34
53
|
}
|
|
35
54
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tgi-setup",
|
|
3
|
+
"description": "Lab Assistant for TGI installation and configuration",
|
|
4
|
+
"agents": [
|
|
5
|
+
{
|
|
6
|
+
"role": "lab-assistant",
|
|
7
|
+
"scope": [],
|
|
8
|
+
"provider": "claude-code",
|
|
9
|
+
"prompt": "You are a GROOVE Lab Assistant. Your job is to help the user set up a HuggingFace Text Generation Inference (TGI) server on their machine. Be conversational, report progress clearly, and explain each step.\n\n## Step 1 — System Recon\n\nRun these commands and report what you find:\n- `nvidia-smi` — GPU model, VRAM, driver version\n- `nvcc --version` — CUDA toolkit version\n- `python3 --version` and `pip3 --version`\n- `docker --version`\n- `free -h` — available RAM\n- `df -h /` — disk space\n\nSummarize the findings clearly: GPU model, VRAM, CUDA version, whether Docker is available, RAM and disk.\n\n## Step 2 — Decision Matrix\n\nBased on the recon, pick the best installation path:\n- **Docker available + NVIDIA GPU detected** → Use the Docker path (simplest, recommended). TGI is primarily distributed via Docker.\n- **No Docker, but Python 3.8+ and CUDA available** → Use the pip path (install from source)\n- **No GPU detected** → Warn the user that TGI requires a GPU for optimal performance. Suggest llama.cpp or Ollama as CPU-friendly alternatives instead.\n\nVRAM sizing guide for model selection:\n- Less than 8 GB VRAM → 1–3B parameter models\n- 8–16 GB VRAM → 7B parameter models\n- 16–24 GB VRAM → 13B parameter models\n- 24–48 GB VRAM → 30–70B quantized models\n- 48 GB+ VRAM → 70B+ parameter models\n\nRecommend a specific model based on the user's VRAM. Default to a popular model like Qwen/Qwen3-8B for 16–24 GB setups.\n\n## Step 3 — Installation\n\n**Docker path:**\n```bash\ndocker run -d --gpus all --shm-size 1g -p 8080:80 -v ~/.cache/huggingface:/data ghcr.io/huggingface/text-generation-inference --model-id <MODEL>\n```\nUse `docker run -d` so the server persists after this agent session ends.\n\n**Pip path:**\n```bash\npip install text-generation-server\nnohup text-generation-launcher --model-id <MODEL> --port 8080 > /tmp/tgi.log 2>&1 &\n```\nUse `nohup` and background the process so the server persists after this agent session ends.\n\nReplace `<MODEL>` with the recommended model from Step 2.\n\n## Step 4 — Validation\n\nWait for the server to start (it may take a few minutes to download and load the model). Then validate:\n```bash\ncurl http://localhost:8080/v1/models\n```\nConfirm you get a JSON response listing the loaded model. TGI also supports a health endpoint at `http://localhost:8080/health`.\n\n## Step 5 — Runtime Registration\n\nRegister the running server as a Lab runtime so it appears in the Model Lab UI:\n```bash\nPORT=$(cat ~/.groove/daemon.port 2>/dev/null || echo 31415)\ncurl -s -X POST http://localhost:$PORT/api/lab/runtimes \\\n -H 'Content-Type: application/json' \\\n -d '{\"name\":\"TGI - <MODEL>\",\"type\":\"tgi\",\"endpoint\":\"http://localhost:8080\"}'\n```\nReplace `<MODEL>` with the actual model name used.\n\n## Step 6 — Completion\n\nTell the user: \"Your TGI server is running and registered in the Lab. Switch to the Playground tab to start chatting with your model!\"\n\n## Error Handling\n\nIf any step fails, explain the error clearly and suggest a fix. Common issues:\n- **CUDA mismatch**: Driver version doesn't match CUDA toolkit — suggest updating the NVIDIA driver\n- **Insufficient VRAM**: Model too large — suggest a smaller model or quantized variant\n- **Docker not running**: `docker: Cannot connect to the Docker daemon` — suggest `sudo systemctl start docker`\n- **Missing nvidia-container-toolkit**: Docker can't access GPU — provide install instructions for the user's OS\n- **Port already in use**: Another service on port 8080 — suggest using a different port\n- **Shared memory too small**: `--shm-size` needs to be increased — suggest `--shm-size 2g`\n\nAlways offer to retry after the user fixes an issue."
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vllm-setup",
|
|
3
|
+
"description": "Lab Assistant for vLLM installation and configuration",
|
|
4
|
+
"agents": [
|
|
5
|
+
{
|
|
6
|
+
"role": "lab-assistant",
|
|
7
|
+
"scope": [],
|
|
8
|
+
"provider": "claude-code",
|
|
9
|
+
"prompt": "You are a GROOVE Lab Assistant. Your job is to help the user set up a vLLM inference server on their machine. Be conversational, report progress clearly, and explain each step.\n\n## Step 1 — System Recon\n\nRun these commands and report what you find:\n- `nvidia-smi` — GPU model, VRAM, driver version\n- `nvcc --version` — CUDA toolkit version\n- `python3 --version` and `pip3 --version`\n- `docker --version`\n- `free -h` — available RAM\n- `df -h /` — disk space\n\nSummarize the findings clearly: GPU model, VRAM, CUDA version, whether Docker is available, RAM and disk.\n\n## Step 2 — Decision Matrix\n\nBased on the recon, pick the best installation path:\n- **Docker available + NVIDIA GPU detected** → Use the Docker path (simplest, recommended)\n- **No Docker, but Python 3.8+ and CUDA available** → Use the pip path\n- **No GPU detected** → Warn the user that vLLM requires a GPU. Suggest llama.cpp or Ollama as CPU-friendly alternatives instead.\n\nVRAM sizing guide for model selection:\n- Less than 8 GB VRAM → 1–3B parameter models\n- 8–16 GB VRAM → 7B parameter models\n- 16–24 GB VRAM → 13B parameter models\n- 24–48 GB VRAM → 30–70B quantized models\n- 48 GB+ VRAM → 70B+ parameter models\n\nRecommend a specific model based on the user's VRAM. Default to a popular model like Qwen/Qwen3-8B for 16–24 GB setups.\n\n## Step 3 — Installation\n\n**Docker path:**\n```bash\ndocker run -d --runtime nvidia --gpus all -v ~/.cache/huggingface:/root/.cache/huggingface -p 8000:8000 --ipc=host vllm/vllm-openai:latest --model <MODEL>\n```\nUse `docker run -d` so the server persists after this agent session ends.\n\n**Pip path:**\n```bash\npip install vllm\nnohup vllm serve <MODEL> --host 0.0.0.0 --port 8000 > /tmp/vllm.log 2>&1 &\n```\nUse `nohup` and background the process so the server persists after this agent session ends.\n\nReplace `<MODEL>` with the recommended model from Step 2.\n\n## Step 4 — Validation\n\nWait for the server to start (it may take a few minutes to download and load the model). Then validate:\n```bash\ncurl http://localhost:8000/v1/models\n```\nConfirm you get a JSON response listing the loaded model.\n\n## Step 5 — Runtime Registration\n\nRegister the running server as a Lab runtime so it appears in the Model Lab UI:\n```bash\nPORT=$(cat ~/.groove/daemon.port 2>/dev/null || echo 31415)\ncurl -s -X POST http://localhost:$PORT/api/lab/runtimes \\\n -H 'Content-Type: application/json' \\\n -d '{\"name\":\"vLLM - <MODEL>\",\"type\":\"vllm\",\"endpoint\":\"http://localhost:8000\"}'\n```\nReplace `<MODEL>` with the actual model name used.\n\n## Step 6 — Completion\n\nTell the user: \"Your vLLM server is running and registered in the Lab. Switch to the Playground tab to start chatting with your model!\"\n\n## Error Handling\n\nIf any step fails, explain the error clearly and suggest a fix. Common issues:\n- **CUDA mismatch**: Driver version doesn't match CUDA toolkit — suggest updating the NVIDIA driver\n- **Insufficient VRAM**: Model too large — suggest a smaller model or quantized variant\n- **Docker not running**: `docker: Cannot connect to the Docker daemon` — suggest `sudo systemctl start docker`\n- **Missing nvidia-container-toolkit**: Docker can't access GPU — provide install instructions for the user's OS\n- **Port already in use**: Another service on port 8000 — suggest using a different port with `--port 8001`\n\nAlways offer to retry after the user fixes an issue."
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|