groove-dev 0.27.209 → 0.27.210
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/axom-runtimes.js +67 -1
- package/node_modules/@groove-dev/daemon/src/routes/axom.js +17 -0
- package/node_modules/@groove-dev/daemon/test/axom-runtimes.test.js +62 -0
- package/node_modules/@groove-dev/gui/dist/assets/{index-217ZVIOc.js → index-YHDeARYl.js} +57 -57
- package/node_modules/@groove-dev/gui/dist/index.html +1 -1
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/axom-runtimes.js +67 -1
- package/packages/daemon/src/routes/axom.js +17 -0
- package/packages/gui/dist/assets/{index-217ZVIOc.js → index-YHDeARYl.js} +57 -57
- package/packages/gui/dist/index.html +1 -1
- package/packages/gui/package.json +1 -1
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
8
8
|
<title>Groove GUI</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-YHDeARYl.js"></script>
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/vendor-26L3JoZv.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/reactflow-DoBZjiHE.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/codemirror-BYKpdS2W.js">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.210",
|
|
4
4
|
"description": "Open-source agent orchestration layer — the AI company OS. Local model agent engine (GGUF/Ollama/llama-server), HuggingFace model browser, MCP integrations (Slack, Gmail, Stripe, 15+), agent scheduling (cron), business roles (CMO, CFO, EA). GUI dashboard, multi-agent coordination, zero cold-start, infinite sessions. Works with Claude Code, Codex, Gemini CLI, Ollama, any local model.",
|
|
5
5
|
"license": "FSL-1.1-Apache-2.0",
|
|
6
6
|
"author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",
|
|
@@ -32,6 +32,22 @@ function withBlessedEnv(launch) {
|
|
|
32
32
|
return { ...launch, env: { ...BLESSED_ENV, ...(launch.env || {}) } };
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
// A chat title is the opening message, trimmed to a glanceable length. It is
|
|
36
|
+
// a QUOTE, not a summary: GROOVE has no business paraphrasing what the user
|
|
37
|
+
// said, and an em-dash ellipsis makes the truncation visible rather than
|
|
38
|
+
// pretending the sentence ended there.
|
|
39
|
+
const TITLE_MAX = 48;
|
|
40
|
+
export function summarizeForTitle(text) {
|
|
41
|
+
if (typeof text !== 'string') return null;
|
|
42
|
+
const flat = text.replace(/\s+/g, ' ').trim();
|
|
43
|
+
if (!flat) return null;
|
|
44
|
+
if (flat.length <= TITLE_MAX) return flat;
|
|
45
|
+
// Prefer a word boundary so titles don't end mid-word.
|
|
46
|
+
const cut = flat.slice(0, TITLE_MAX);
|
|
47
|
+
const space = cut.lastIndexOf(' ');
|
|
48
|
+
return `${(space > TITLE_MAX * 0.6 ? cut.slice(0, space) : cut).trimEnd()}…`;
|
|
49
|
+
}
|
|
50
|
+
|
|
35
51
|
// Single-quote for a POSIX shell. The spec is the user's own, but it crosses
|
|
36
52
|
// an ssh command line — an unquoted path or value must not be able to end the
|
|
37
53
|
// command and start another.
|
|
@@ -329,13 +345,29 @@ export class AxomRuntimes {
|
|
|
329
345
|
this._save();
|
|
330
346
|
}
|
|
331
347
|
|
|
348
|
+
// A chat titles itself from what it started with — "Chat 3" tells you
|
|
349
|
+
// nothing when you have six of them. Only ever replaces a PLACEHOLDER title:
|
|
350
|
+
// a name the user typed, or one already derived from the opening message, is
|
|
351
|
+
// never overwritten by a later turn.
|
|
352
|
+
titleFromFirstMessage(session, text) {
|
|
353
|
+
const chat = this.getChat(session);
|
|
354
|
+
if (!chat || chat.titled || chat.renamed) return null;
|
|
355
|
+
const title = summarizeForTitle(text);
|
|
356
|
+
if (!title) return null;
|
|
357
|
+
this._putChat({ ...chat, label: title, titled: true });
|
|
358
|
+
this.broadcastChats();
|
|
359
|
+
return title;
|
|
360
|
+
}
|
|
361
|
+
|
|
332
362
|
renameChat(session, label) {
|
|
333
363
|
const chat = this.getChat(session);
|
|
334
364
|
if (!chat) throw new Error(`no chat "${session}"`);
|
|
335
365
|
if (typeof label !== 'string' || !label.trim() || label.length > 80) {
|
|
336
366
|
throw new Error('label must be a non-empty string of at most 80 chars');
|
|
337
367
|
}
|
|
338
|
-
|
|
368
|
+
// `renamed` is sticky: once the user names a chat, no later auto-title
|
|
369
|
+
// may take it back.
|
|
370
|
+
this._putChat({ ...chat, label: label.trim(), renamed: true });
|
|
339
371
|
this.broadcastChats();
|
|
340
372
|
return this.getChat(session);
|
|
341
373
|
}
|
|
@@ -348,10 +380,44 @@ export class AxomRuntimes {
|
|
|
348
380
|
const chat = this.getChat(session);
|
|
349
381
|
if (!chat) throw new Error(`no chat "${session}"`);
|
|
350
382
|
this._putChat({ ...chat, hidden: true });
|
|
383
|
+
this._forgetPrompts(session);
|
|
384
|
+
this._save();
|
|
351
385
|
this.broadcastChats();
|
|
352
386
|
return { hidden: true, session, note: 'removed from the list; the conversation remains in Axom\'s memory' };
|
|
353
387
|
}
|
|
354
388
|
|
|
389
|
+
// ── Prompts — what GROOVE sent, remembered where the events are ──────────
|
|
390
|
+
//
|
|
391
|
+
// The runtime's `pipeline_start` carries no prompt text, so the user's own
|
|
392
|
+
// words exist only in GROOVE. Keeping them in the browser meant a reload
|
|
393
|
+
// replayed every turn from the daemon's ring with its bubble gone — the
|
|
394
|
+
// answer with no question above it. This is OUR record of what WE sent, not
|
|
395
|
+
// invented telemetry, so the daemon is the right place for it.
|
|
396
|
+
recordPrompt(session, ref, text) {
|
|
397
|
+
if (!session || !ref) return null;
|
|
398
|
+
const all = this._cfg().prompts || {};
|
|
399
|
+
const forSession = (all[session] || []).filter((p) => p.ref !== ref);
|
|
400
|
+
// Bounded per session: a transcript this long is scrollback, not memory.
|
|
401
|
+
const next = [...forSession, { ref, text, ts: Date.now() }].slice(-200);
|
|
402
|
+
this._cfg().prompts = { ...all, [session]: next };
|
|
403
|
+
this._save();
|
|
404
|
+
return { ref, text };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
prompts(session) {
|
|
408
|
+
return (this._cfg().prompts || {})[session] || [];
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// A hidden chat's prompts go with it — the list is tidied, the ledger keeps
|
|
412
|
+
// the conversation itself.
|
|
413
|
+
_forgetPrompts(session) {
|
|
414
|
+
const all = this._cfg().prompts || {};
|
|
415
|
+
if (!all[session]) return;
|
|
416
|
+
const next = { ...all };
|
|
417
|
+
delete next[session];
|
|
418
|
+
this._cfg().prompts = next;
|
|
419
|
+
}
|
|
420
|
+
|
|
355
421
|
broadcastChats() {
|
|
356
422
|
this.daemon.broadcast({ type: 'axom:chats', data: { chats: this.chats() } });
|
|
357
423
|
}
|
|
@@ -46,6 +46,16 @@ export function registerAxomRoutes(app, daemon) {
|
|
|
46
46
|
return res.status(400).json({ error: 'clientRef must be a string of at most 64 chars' });
|
|
47
47
|
}
|
|
48
48
|
const result = await daemon.axom.message(endpoint, req.params.id, text, clientRef);
|
|
49
|
+
// Title the chat from its opening message — but only once the runtime
|
|
50
|
+
// ACCEPTED the turn. A message rejected with 409/413 never ran, so it
|
|
51
|
+
// must not name the conversation it failed to start.
|
|
52
|
+
if (result.status === 202) {
|
|
53
|
+
daemon.axomRuntimes.titleFromFirstMessage(req.params.id, text);
|
|
54
|
+
// Remember what we sent, keyed by the §15 ref the runtime echoes in
|
|
55
|
+
// pipeline_start. This is what lets a reloaded tab put the user's own
|
|
56
|
+
// words back above the answer instead of "prompt not identified".
|
|
57
|
+
if (clientRef) daemon.axomRuntimes.recordPrompt(req.params.id, clientRef, text);
|
|
58
|
+
}
|
|
49
59
|
daemon.audit.log('axom.message', { session: req.params.id, chars: text.length, status: result.status });
|
|
50
60
|
res.status(result.status).json(result.body);
|
|
51
61
|
} catch (err) {
|
|
@@ -167,6 +177,13 @@ export function registerAxomRoutes(app, daemon) {
|
|
|
167
177
|
res.json({ chats: daemon.axomRuntimes.chats() });
|
|
168
178
|
});
|
|
169
179
|
|
|
180
|
+
// What GROOVE sent on this session, so a reloaded tab can restore the user's
|
|
181
|
+
// bubbles. Only ever OUR OWN sends — a turn started from the REPL or another
|
|
182
|
+
// client has no entry here and must still render without a bubble.
|
|
183
|
+
app.get('/api/axom/sessions/:id/prompts', (req, res) => {
|
|
184
|
+
res.json({ prompts: daemon.axomRuntimes.prompts(req.params.id) });
|
|
185
|
+
});
|
|
186
|
+
|
|
170
187
|
app.patch('/api/axom/chats/:session', (req, res) => {
|
|
171
188
|
try {
|
|
172
189
|
res.json(daemon.axomRuntimes.renameChat(req.params.session, req.body?.label));
|