groove-dev 0.14.2 → 0.15.1
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 +37 -0
- package/node_modules/@groove-dev/daemon/src/introducer.js +26 -0
- package/node_modules/@groove-dev/daemon/src/registry.js +2 -1
- package/node_modules/@groove-dev/daemon/src/skills.js +20 -0
- package/node_modules/@groove-dev/daemon/src/validate.js +10 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-8Kqi_LVo.js +74 -0
- package/node_modules/@groove-dev/gui/dist/index.html +1 -1
- package/node_modules/@groove-dev/gui/src/components/AgentActions.jsx +129 -0
- package/node_modules/@groove-dev/gui/src/components/SpawnPanel.jsx +84 -0
- package/node_modules/@groove-dev/gui/src/views/SkillsMarketplace.jsx +113 -2
- package/package.json +1 -1
- package/packages/daemon/src/api.js +37 -0
- package/packages/daemon/src/introducer.js +26 -0
- package/packages/daemon/src/registry.js +2 -1
- package/packages/daemon/src/skills.js +20 -0
- package/packages/daemon/src/validate.js +10 -0
- package/packages/gui/dist/assets/index-8Kqi_LVo.js +74 -0
- package/packages/gui/dist/index.html +1 -1
- package/packages/gui/src/components/AgentActions.jsx +129 -0
- package/packages/gui/src/components/SpawnPanel.jsx +84 -0
- package/packages/gui/src/views/SkillsMarketplace.jsx +113 -2
- package/node_modules/@groove-dev/gui/dist/assets/index-TcP3URUY.js +0 -74
- package/packages/gui/dist/assets/index-TcP3URUY.js +0 -74
|
@@ -400,12 +400,49 @@ export function createApi(app, daemon) {
|
|
|
400
400
|
}
|
|
401
401
|
});
|
|
402
402
|
|
|
403
|
+
app.post('/api/skills/:id/rate', async (req, res) => {
|
|
404
|
+
try {
|
|
405
|
+
const rating = parseInt(req.body?.rating, 10);
|
|
406
|
+
const result = await daemon.skills.rate(req.params.id, rating);
|
|
407
|
+
res.json(result);
|
|
408
|
+
} catch (err) {
|
|
409
|
+
res.status(400).json({ error: err.message });
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
|
|
403
413
|
app.get('/api/skills/:id/content', (req, res) => {
|
|
404
414
|
const content = daemon.skills.getContent(req.params.id);
|
|
405
415
|
if (!content) return res.status(404).json({ error: 'Skill not installed' });
|
|
406
416
|
res.json({ id: req.params.id, content });
|
|
407
417
|
});
|
|
408
418
|
|
|
419
|
+
// --- Agent Skills (attach/detach) ---
|
|
420
|
+
|
|
421
|
+
app.post('/api/agents/:agentId/skills/:skillId', (req, res) => {
|
|
422
|
+
const agent = daemon.registry.get(req.params.agentId);
|
|
423
|
+
if (!agent) return res.status(404).json({ error: 'Agent not found' });
|
|
424
|
+
const skillId = req.params.skillId;
|
|
425
|
+
if (!daemon.skills.getContent(skillId)) {
|
|
426
|
+
return res.status(400).json({ error: 'Skill not installed. Install it first.' });
|
|
427
|
+
}
|
|
428
|
+
const skills = agent.skills || [];
|
|
429
|
+
if (skills.includes(skillId)) {
|
|
430
|
+
return res.json({ id: agent.id, skills });
|
|
431
|
+
}
|
|
432
|
+
daemon.registry.update(agent.id, { skills: [...skills, skillId] });
|
|
433
|
+
daemon.audit.log('skill.attach', { agentId: agent.id, skillId });
|
|
434
|
+
res.json({ id: agent.id, skills: [...skills, skillId] });
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
app.delete('/api/agents/:agentId/skills/:skillId', (req, res) => {
|
|
438
|
+
const agent = daemon.registry.get(req.params.agentId);
|
|
439
|
+
if (!agent) return res.status(404).json({ error: 'Agent not found' });
|
|
440
|
+
const skills = (agent.skills || []).filter((s) => s !== req.params.skillId);
|
|
441
|
+
daemon.registry.update(agent.id, { skills });
|
|
442
|
+
daemon.audit.log('skill.detach', { agentId: agent.id, skillId: req.params.skillId });
|
|
443
|
+
res.json({ id: agent.id, skills });
|
|
444
|
+
});
|
|
445
|
+
|
|
409
446
|
// --- Directory Browser ---
|
|
410
447
|
|
|
411
448
|
app.get('/api/browse', (req, res) => {
|
|
@@ -162,6 +162,32 @@ export class Introducer {
|
|
|
162
162
|
lines.push(archContent);
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
// Skills injection — load attached skill content and inject into context
|
|
166
|
+
if (newAgent.skills && newAgent.skills.length > 0 && this.daemon.skills) {
|
|
167
|
+
const skillSections = [];
|
|
168
|
+
for (const skillId of newAgent.skills) {
|
|
169
|
+
const content = this.daemon.skills.getContent(skillId);
|
|
170
|
+
if (content) {
|
|
171
|
+
// Strip YAML frontmatter, keep the instruction body
|
|
172
|
+
const body = content.replace(/^---[\s\S]*?---\n*/, '').trim();
|
|
173
|
+
if (body) {
|
|
174
|
+
// Find the skill name from registry or frontmatter
|
|
175
|
+
const regEntry = this.daemon.skills.registry.find((s) => s.id === skillId);
|
|
176
|
+
const name = regEntry?.name || skillId;
|
|
177
|
+
skillSections.push(`### ${name}\n\n${body}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (skillSections.length > 0) {
|
|
182
|
+
lines.push('');
|
|
183
|
+
lines.push(`## Skills (${skillSections.length} attached)`);
|
|
184
|
+
lines.push('');
|
|
185
|
+
lines.push(`The following skills have been attached to this agent. Follow their instructions:`);
|
|
186
|
+
lines.push('');
|
|
187
|
+
lines.push(skillSections.join('\n\n---\n\n'));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
165
191
|
return lines.join('\n');
|
|
166
192
|
}
|
|
167
193
|
|
|
@@ -22,6 +22,7 @@ export class Registry extends EventEmitter {
|
|
|
22
22
|
prompt: config.prompt || '',
|
|
23
23
|
permission: config.permission || 'full',
|
|
24
24
|
workingDir: config.workingDir || process.cwd(),
|
|
25
|
+
skills: config.skills || [],
|
|
25
26
|
status: 'starting',
|
|
26
27
|
pid: null,
|
|
27
28
|
spawnedAt: new Date().toISOString(),
|
|
@@ -48,7 +49,7 @@ export class Registry extends EventEmitter {
|
|
|
48
49
|
if (!agent) return null;
|
|
49
50
|
|
|
50
51
|
// Only allow known fields to prevent prototype pollution
|
|
51
|
-
const SAFE_FIELDS = ['status', 'pid', 'tokensUsed', 'contextUsage', 'lastActivity', 'model', 'name', 'routingMode', 'routingReason', 'sessionId'];
|
|
52
|
+
const SAFE_FIELDS = ['status', 'pid', 'tokensUsed', 'contextUsage', 'lastActivity', 'model', 'name', 'routingMode', 'routingReason', 'sessionId', 'skills'];
|
|
52
53
|
for (const key of Object.keys(updates)) {
|
|
53
54
|
if (SAFE_FIELDS.includes(key)) {
|
|
54
55
|
agent[key] = updates[key];
|
|
@@ -193,6 +193,26 @@ export class SkillStore {
|
|
|
193
193
|
return { id: skillId, installed: false };
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Rate a skill. Proxies to the skills server.
|
|
198
|
+
*/
|
|
199
|
+
async rate(skillId, rating) {
|
|
200
|
+
if (!Number.isInteger(rating) || rating < 1 || rating > 5) {
|
|
201
|
+
throw new Error('Rating must be an integer from 1 to 5');
|
|
202
|
+
}
|
|
203
|
+
const res = await fetch(`${SKILLS_API}/skills/${skillId}/rate`, {
|
|
204
|
+
method: 'POST',
|
|
205
|
+
headers: { 'Content-Type': 'application/json' },
|
|
206
|
+
body: JSON.stringify({ rating }),
|
|
207
|
+
signal: AbortSignal.timeout(10000),
|
|
208
|
+
});
|
|
209
|
+
if (!res.ok) {
|
|
210
|
+
const data = await res.json().catch(() => ({}));
|
|
211
|
+
throw new Error(data.error || 'Rating failed');
|
|
212
|
+
}
|
|
213
|
+
return res.json();
|
|
214
|
+
}
|
|
215
|
+
|
|
196
216
|
/**
|
|
197
217
|
* Get the full content of an installed skill.
|
|
198
218
|
*/
|
|
@@ -57,6 +57,15 @@ export function validateAgentConfig(config) {
|
|
|
57
57
|
const validPermissions = ['auto', 'full'];
|
|
58
58
|
const permission = validPermissions.includes(config.permission) ? config.permission : 'full';
|
|
59
59
|
|
|
60
|
+
// Validate skills (array of skill IDs)
|
|
61
|
+
let skills = [];
|
|
62
|
+
if (config.skills !== undefined && config.skills !== null) {
|
|
63
|
+
if (!Array.isArray(config.skills)) {
|
|
64
|
+
throw new Error('Skills must be an array');
|
|
65
|
+
}
|
|
66
|
+
skills = config.skills.filter((s) => typeof s === 'string' && s.length > 0 && s.length <= 100);
|
|
67
|
+
}
|
|
68
|
+
|
|
60
69
|
// Return sanitized config (only known fields)
|
|
61
70
|
return {
|
|
62
71
|
role: config.role,
|
|
@@ -67,6 +76,7 @@ export function validateAgentConfig(config) {
|
|
|
67
76
|
model: typeof config.model === 'string' ? config.model : null,
|
|
68
77
|
workingDir: typeof config.workingDir === 'string' ? config.workingDir : undefined,
|
|
69
78
|
permission,
|
|
79
|
+
skills,
|
|
70
80
|
};
|
|
71
81
|
}
|
|
72
82
|
|