groove-dev 0.27.175 → 0.27.179
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/AUTONOMOUS_AGENT_FRAMEWORK.md +654 -0
- package/innerchat/Screenshot_2026-06-09_at_2.19.42_PM.png +0 -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 +3 -1
- package/node_modules/@groove-dev/daemon/src/autostate.js +198 -0
- package/node_modules/@groove-dev/daemon/src/index.js +6 -0
- package/node_modules/@groove-dev/daemon/src/orchestrator.js +585 -0
- package/node_modules/@groove-dev/daemon/src/process.js +27 -2
- package/node_modules/@groove-dev/daemon/src/registry.js +2 -1
- package/node_modules/@groove-dev/daemon/src/routes/auto-agents.js +264 -0
- package/node_modules/@groove-dev/daemon/src/validate.js +6 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-CTer01Vg.js +1065 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-DTFtRtkx.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/App.jsx +2 -0
- package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +53 -0
- package/node_modules/@groove-dev/gui/src/components/agents/agent-panel.jsx +95 -2
- package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +50 -1
- package/node_modules/@groove-dev/gui/src/components/auto-agents/auto-agent-card.jsx +139 -0
- package/node_modules/@groove-dev/gui/src/components/auto-agents/auto-agent-detail.jsx +286 -0
- package/node_modules/@groove-dev/gui/src/components/auto-agents/setup-wizard.jsx +284 -0
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +2 -1
- package/node_modules/@groove-dev/gui/src/stores/groove.js +14 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/auto-agents-slice.js +149 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/ui-slice.js +14 -2
- package/node_modules/@groove-dev/gui/src/views/auto-agents.jsx +70 -0
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +13 -6
- 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 +3 -1
- package/packages/daemon/src/autostate.js +198 -0
- package/packages/daemon/src/index.js +6 -0
- package/packages/daemon/src/orchestrator.js +585 -0
- package/packages/daemon/src/process.js +27 -2
- package/packages/daemon/src/registry.js +2 -1
- package/packages/daemon/src/routes/auto-agents.js +264 -0
- package/packages/daemon/src/validate.js +6 -0
- package/packages/gui/dist/assets/index-CTer01Vg.js +1065 -0
- package/packages/gui/dist/assets/index-DTFtRtkx.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/App.jsx +2 -0
- package/packages/gui/src/components/agents/agent-config.jsx +53 -0
- package/packages/gui/src/components/agents/agent-panel.jsx +95 -2
- package/packages/gui/src/components/agents/spawn-wizard.jsx +50 -1
- package/packages/gui/src/components/auto-agents/auto-agent-card.jsx +139 -0
- package/packages/gui/src/components/auto-agents/auto-agent-detail.jsx +286 -0
- package/packages/gui/src/components/auto-agents/setup-wizard.jsx +284 -0
- package/packages/gui/src/components/layout/activity-bar.jsx +2 -1
- package/packages/gui/src/stores/groove.js +14 -0
- package/packages/gui/src/stores/slices/auto-agents-slice.js +149 -0
- package/packages/gui/src/stores/slices/ui-slice.js +14 -2
- package/packages/gui/src/views/auto-agents.jsx +70 -0
- package/packages/gui/src/views/settings.jsx +13 -6
- package/node_modules/@groove-dev/gui/dist/assets/index-BMaxZVeX.js +0 -1035
- package/node_modules/@groove-dev/gui/dist/assets/index-BvJwMNAX.css +0 -1
- package/packages/gui/dist/assets/index-BMaxZVeX.js +0 -1035
- package/packages/gui/dist/assets/index-BvJwMNAX.css +0 -1
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
// GROOVE — Auto Agent API Routes
|
|
2
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
3
|
+
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
import { getProvider, resolveProviderCommand } from '../providers/index.js';
|
|
6
|
+
|
|
7
|
+
function execHeadlessChat(daemon, messages, systemPrompt) {
|
|
8
|
+
const provider = getProvider('claude-code');
|
|
9
|
+
if (!provider) return Promise.reject(new Error('Claude Code provider not available'));
|
|
10
|
+
|
|
11
|
+
const fullPrompt = systemPrompt + '\n\n' + messages.map(m =>
|
|
12
|
+
`${m.role === 'user' ? 'Human' : 'Assistant'}: ${m.content}`
|
|
13
|
+
).join('\n\n') + '\n\nAssistant:';
|
|
14
|
+
|
|
15
|
+
const cmd = resolveProviderCommand('claude-code') || 'claude';
|
|
16
|
+
const args = ['-p', '--output-format', 'text'];
|
|
17
|
+
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
let stdout = '';
|
|
20
|
+
const proc = spawn(cmd, args, {
|
|
21
|
+
env: process.env,
|
|
22
|
+
cwd: daemon.projectDir,
|
|
23
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
24
|
+
});
|
|
25
|
+
proc.on('error', reject);
|
|
26
|
+
proc.stdin.write(fullPrompt);
|
|
27
|
+
proc.stdin.end();
|
|
28
|
+
proc.stdout.on('data', (d) => { stdout += d.toString(); });
|
|
29
|
+
const timer = setTimeout(() => { proc.kill(); reject(new Error('Setup chat timeout')); }, 120_000);
|
|
30
|
+
proc.on('exit', (code) => {
|
|
31
|
+
clearTimeout(timer);
|
|
32
|
+
if (code !== 0) return reject(new Error(`Headless exited with code ${code}`));
|
|
33
|
+
|
|
34
|
+
// Try to parse stream-json output
|
|
35
|
+
let response = '';
|
|
36
|
+
for (const line of stdout.split('\n')) {
|
|
37
|
+
try {
|
|
38
|
+
const json = JSON.parse(line);
|
|
39
|
+
if (typeof json.result === 'string') { response = json.result; break; }
|
|
40
|
+
if (json.type === 'assistant' && typeof json.message?.content === 'string') {
|
|
41
|
+
response = json.message.content;
|
|
42
|
+
}
|
|
43
|
+
if (json.type === 'assistant' && Array.isArray(json.message?.content)) {
|
|
44
|
+
const text = json.message.content.filter(b => b.type === 'text').map(b => b.text).join('');
|
|
45
|
+
if (text) response = text;
|
|
46
|
+
}
|
|
47
|
+
} catch { /* not json */ }
|
|
48
|
+
}
|
|
49
|
+
if (!response) response = stdout.trim();
|
|
50
|
+
|
|
51
|
+
// Extract config if present
|
|
52
|
+
let config = null;
|
|
53
|
+
const jsonMatch = response.match(/```json\s*([\s\S]*?)```/);
|
|
54
|
+
if (jsonMatch) {
|
|
55
|
+
try {
|
|
56
|
+
const parsed = JSON.parse(jsonMatch[1]);
|
|
57
|
+
if (parsed.ready) config = parsed;
|
|
58
|
+
} catch { /* not valid json */ }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
resolve({ response, config });
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function registerAutoAgentRoutes(app, daemon) {
|
|
67
|
+
|
|
68
|
+
// AI-assisted setup chat (must be before :id routes)
|
|
69
|
+
app.post('/api/auto-agents/setup-chat', async (req, res) => {
|
|
70
|
+
const { messages, systemPrompt } = req.body;
|
|
71
|
+
if (!messages || !Array.isArray(messages)) {
|
|
72
|
+
return res.status(400).json({ error: 'messages array is required' });
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const result = await execHeadlessChat(daemon, messages, systemPrompt || '');
|
|
76
|
+
res.json(result);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
res.status(500).json({ error: err.message });
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// List all auto agent definitions
|
|
83
|
+
app.get('/api/auto-agents', (req, res) => {
|
|
84
|
+
const defs = daemon.orchestrator.list();
|
|
85
|
+
const enriched = defs.map(def => {
|
|
86
|
+
const state = daemon.autoState.getState(def.id);
|
|
87
|
+
const activeAgentId = daemon.orchestrator.activeAgents.get(def.id);
|
|
88
|
+
let activeAgent = null;
|
|
89
|
+
if (activeAgentId) {
|
|
90
|
+
const agent = daemon.registry.get(activeAgentId);
|
|
91
|
+
if (agent) activeAgent = { id: agent.id, status: agent.status, spawnedAt: agent.spawnedAt };
|
|
92
|
+
}
|
|
93
|
+
return { ...def, state, activeAgent };
|
|
94
|
+
});
|
|
95
|
+
res.json(enriched);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Create a new auto agent
|
|
99
|
+
app.post('/api/auto-agents', (req, res) => {
|
|
100
|
+
try {
|
|
101
|
+
const def = daemon.orchestrator.create(req.body);
|
|
102
|
+
res.status(201).json(def);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
res.status(400).json({ error: err.message });
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Get a single auto agent with full status
|
|
109
|
+
app.get('/api/auto-agents/:id', (req, res) => {
|
|
110
|
+
const status = daemon.orchestrator.getStatus(req.params.id);
|
|
111
|
+
if (!status) return res.status(404).json({ error: 'Auto agent not found' });
|
|
112
|
+
res.json(status);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Update an auto agent definition
|
|
116
|
+
app.patch('/api/auto-agents/:id', (req, res) => {
|
|
117
|
+
try {
|
|
118
|
+
const def = daemon.orchestrator.update(req.params.id, req.body);
|
|
119
|
+
res.json(def);
|
|
120
|
+
} catch (err) {
|
|
121
|
+
res.status(400).json({ error: err.message });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Delete an auto agent
|
|
126
|
+
app.delete('/api/auto-agents/:id', (req, res) => {
|
|
127
|
+
try {
|
|
128
|
+
daemon.orchestrator.delete(req.params.id);
|
|
129
|
+
res.json({ ok: true });
|
|
130
|
+
} catch (err) {
|
|
131
|
+
res.status(400).json({ error: err.message });
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Pause
|
|
136
|
+
app.post('/api/auto-agents/:id/pause', (req, res) => {
|
|
137
|
+
try {
|
|
138
|
+
const def = daemon.orchestrator.pause(req.params.id);
|
|
139
|
+
res.json(def);
|
|
140
|
+
} catch (err) {
|
|
141
|
+
res.status(400).json({ error: err.message });
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Resume
|
|
146
|
+
app.post('/api/auto-agents/:id/resume', (req, res) => {
|
|
147
|
+
try {
|
|
148
|
+
const def = daemon.orchestrator.resume(req.params.id);
|
|
149
|
+
res.json(def);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
res.status(400).json({ error: err.message });
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Trigger now (manual iteration)
|
|
156
|
+
app.post('/api/auto-agents/:id/trigger', async (req, res) => {
|
|
157
|
+
try {
|
|
158
|
+
const agent = await daemon.orchestrator.trigger(req.params.id);
|
|
159
|
+
res.json({ ok: true, agentId: agent.id });
|
|
160
|
+
} catch (err) {
|
|
161
|
+
res.status(400).json({ error: err.message });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// --- State Layer ---
|
|
166
|
+
|
|
167
|
+
// Get state
|
|
168
|
+
app.get('/api/auto-agents/:id/state', (req, res) => {
|
|
169
|
+
const def = daemon.orchestrator.get(req.params.id);
|
|
170
|
+
if (!def) return res.status(404).json({ error: 'Auto agent not found' });
|
|
171
|
+
res.json(daemon.autoState.getState(req.params.id));
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Update state (agent or human can call this to redirect)
|
|
175
|
+
app.patch('/api/auto-agents/:id/state', (req, res) => {
|
|
176
|
+
const def = daemon.orchestrator.get(req.params.id);
|
|
177
|
+
if (!def) return res.status(404).json({ error: 'Auto agent not found' });
|
|
178
|
+
try {
|
|
179
|
+
const state = daemon.autoState.setState(req.params.id, req.body);
|
|
180
|
+
daemon.broadcast({ type: 'auto-agent:state-updated', data: { id: req.params.id, state } });
|
|
181
|
+
res.json(state);
|
|
182
|
+
} catch (err) {
|
|
183
|
+
res.status(400).json({ error: err.message });
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Get journal
|
|
188
|
+
app.get('/api/auto-agents/:id/journal', (req, res) => {
|
|
189
|
+
const def = daemon.orchestrator.get(req.params.id);
|
|
190
|
+
if (!def) return res.status(404).json({ error: 'Auto agent not found' });
|
|
191
|
+
const limit = parseInt(req.query.limit) || 50;
|
|
192
|
+
const since = req.query.since || undefined;
|
|
193
|
+
res.json(daemon.autoState.getJournal(req.params.id, { limit, since }));
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Append journal entry
|
|
197
|
+
app.post('/api/auto-agents/:id/journal', (req, res) => {
|
|
198
|
+
const def = daemon.orchestrator.get(req.params.id);
|
|
199
|
+
if (!def) return res.status(404).json({ error: 'Auto agent not found' });
|
|
200
|
+
try {
|
|
201
|
+
const entry = daemon.autoState.appendJournal(req.params.id, req.body);
|
|
202
|
+
daemon.broadcast({ type: 'auto-agent:journal-entry', data: { id: req.params.id, entry } });
|
|
203
|
+
res.status(201).json(entry);
|
|
204
|
+
} catch (err) {
|
|
205
|
+
res.status(400).json({ error: err.message });
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Append history entry
|
|
210
|
+
app.post('/api/auto-agents/:id/history', (req, res) => {
|
|
211
|
+
const def = daemon.orchestrator.get(req.params.id);
|
|
212
|
+
if (!def) return res.status(404).json({ error: 'Auto agent not found' });
|
|
213
|
+
try {
|
|
214
|
+
const history = daemon.autoState.appendHistory(req.params.id, req.body);
|
|
215
|
+
daemon.broadcast({ type: 'auto-agent:history-entry', data: { id: req.params.id } });
|
|
216
|
+
res.json({ ok: true, count: history.length });
|
|
217
|
+
} catch (err) {
|
|
218
|
+
res.status(400).json({ error: err.message });
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Get/set roadmap
|
|
223
|
+
app.get('/api/auto-agents/:id/roadmap', (req, res) => {
|
|
224
|
+
const def = daemon.orchestrator.get(req.params.id);
|
|
225
|
+
if (!def) return res.status(404).json({ error: 'Auto agent not found' });
|
|
226
|
+
res.type('text/markdown').send(daemon.autoState.getRoadmap(req.params.id));
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
app.put('/api/auto-agents/:id/roadmap', (req, res) => {
|
|
230
|
+
const def = daemon.orchestrator.get(req.params.id);
|
|
231
|
+
if (!def) return res.status(404).json({ error: 'Auto agent not found' });
|
|
232
|
+
const content = typeof req.body === 'string' ? req.body : req.body.content;
|
|
233
|
+
if (!content) return res.status(400).json({ error: 'Content is required' });
|
|
234
|
+
daemon.autoState.setRoadmap(req.params.id, content);
|
|
235
|
+
daemon.broadcast({ type: 'auto-agent:roadmap-updated', data: { id: req.params.id } });
|
|
236
|
+
res.json({ ok: true });
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Get/set prompt
|
|
240
|
+
app.get('/api/auto-agents/:id/prompt', (req, res) => {
|
|
241
|
+
const def = daemon.orchestrator.get(req.params.id);
|
|
242
|
+
if (!def) return res.status(404).json({ error: 'Auto agent not found' });
|
|
243
|
+
res.type('text/markdown').send(daemon.autoState.getPrompt(req.params.id));
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
app.put('/api/auto-agents/:id/prompt', (req, res) => {
|
|
247
|
+
const def = daemon.orchestrator.get(req.params.id);
|
|
248
|
+
if (!def) return res.status(404).json({ error: 'Auto agent not found' });
|
|
249
|
+
const content = typeof req.body === 'string' ? req.body : req.body.content;
|
|
250
|
+
if (!content) return res.status(400).json({ error: 'Content is required' });
|
|
251
|
+
daemon.autoState.setPrompt(req.params.id, content);
|
|
252
|
+
daemon.broadcast({ type: 'auto-agent:prompt-updated', data: { id: req.params.id } });
|
|
253
|
+
res.json({ ok: true });
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Run history
|
|
257
|
+
app.get('/api/auto-agents/:id/runs', (req, res) => {
|
|
258
|
+
const def = daemon.orchestrator.get(req.params.id);
|
|
259
|
+
if (!def) return res.status(404).json({ error: 'Auto agent not found' });
|
|
260
|
+
const limit = parseInt(req.query.limit) || 20;
|
|
261
|
+
res.json(daemon.autoState.listRuns(req.params.id, { limit }));
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
}
|
|
@@ -118,6 +118,11 @@ export function validateAgentConfig(config) {
|
|
|
118
118
|
const validRouting = ['fixed', 'auto', 'auto-floor'];
|
|
119
119
|
const routingMode = validRouting.includes(config.routingMode) ? config.routingMode : undefined;
|
|
120
120
|
|
|
121
|
+
// Claude Code billing mode: 'subscription' (OAuth login) or 'usage' (API credits).
|
|
122
|
+
// Only meaningful for the claude-code provider; ignored by others.
|
|
123
|
+
const authMode = (config.authMode === 'usage' || config.authMode === 'subscription')
|
|
124
|
+
? config.authMode : undefined;
|
|
125
|
+
|
|
121
126
|
// Return sanitized config (only known fields)
|
|
122
127
|
return {
|
|
123
128
|
role: config.role,
|
|
@@ -139,6 +144,7 @@ export function validateAgentConfig(config) {
|
|
|
139
144
|
verbosity,
|
|
140
145
|
effort,
|
|
141
146
|
routingMode,
|
|
147
|
+
authMode,
|
|
142
148
|
fast: config.fast === true ? true : undefined,
|
|
143
149
|
labPresetId: (typeof config.labPresetId === 'string' && config.labPresetId.length <= 64) ? config.labPresetId : undefined,
|
|
144
150
|
keeperTags: Array.isArray(config.keeperTags) ? config.keeperTags.filter(t => typeof t === 'string').slice(0, 20) : undefined,
|