groove-dev 0.27.182 → 0.27.184
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/CLAUDE.md +0 -7
- 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/deliver.js +130 -0
- package/node_modules/@groove-dev/daemon/src/index.js +7 -3
- package/node_modules/@groove-dev/daemon/src/innerchat-docs.js +41 -0
- package/node_modules/@groove-dev/daemon/src/innerchat.js +267 -60
- package/node_modules/@groove-dev/daemon/src/introducer.js +30 -1
- package/node_modules/@groove-dev/daemon/src/process.js +22 -1
- package/node_modules/@groove-dev/daemon/src/registry.js +5 -1
- package/node_modules/@groove-dev/daemon/src/rename.js +72 -0
- package/node_modules/@groove-dev/daemon/src/routes/agents.js +22 -100
- package/node_modules/@groove-dev/daemon/src/routes/innerchat.js +63 -16
- package/node_modules/@groove-dev/daemon/src/routes/teams.js +11 -0
- package/node_modules/@groove-dev/daemon/src/teams.js +35 -1
- package/node_modules/@groove-dev/daemon/test/innerchat.test.js +193 -134
- package/node_modules/@groove-dev/daemon/test/rename.test.js +108 -0
- package/node_modules/@groove-dev/daemon/test/teams.test.js +53 -0
- package/node_modules/@groove-dev/gui/dist/assets/{index-CTer01Vg.js → index-BpOyN6Zf.js} +227 -227
- package/node_modules/@groove-dev/gui/dist/assets/index-DiXB7yry.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/agents/agent-panel.jsx +2 -95
- package/node_modules/@groove-dev/gui/src/components/chat/chat-messages.jsx +3 -1
- package/node_modules/@groove-dev/gui/src/components/fleet/fleet-agent-row.jsx +18 -2
- package/node_modules/@groove-dev/gui/src/components/fleet/fleet-sidebar.jsx +56 -6
- package/node_modules/@groove-dev/gui/src/stores/groove.js +35 -44
- package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +27 -4
- package/node_modules/@groove-dev/gui/src/stores/slices/teams-slice.js +22 -0
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/deliver.js +130 -0
- package/packages/daemon/src/index.js +7 -3
- package/packages/daemon/src/innerchat-docs.js +41 -0
- package/packages/daemon/src/innerchat.js +267 -60
- package/packages/daemon/src/introducer.js +30 -1
- package/packages/daemon/src/process.js +22 -1
- package/packages/daemon/src/registry.js +5 -1
- package/packages/daemon/src/rename.js +72 -0
- package/packages/daemon/src/routes/agents.js +22 -100
- package/packages/daemon/src/routes/innerchat.js +63 -16
- package/packages/daemon/src/routes/teams.js +11 -0
- package/packages/daemon/src/teams.js +35 -1
- package/packages/gui/dist/assets/{index-CTer01Vg.js → index-BpOyN6Zf.js} +227 -227
- package/packages/gui/dist/assets/index-DiXB7yry.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/agents/agent-panel.jsx +2 -95
- package/packages/gui/src/components/chat/chat-messages.jsx +3 -1
- package/packages/gui/src/components/fleet/fleet-agent-row.jsx +18 -2
- package/packages/gui/src/components/fleet/fleet-sidebar.jsx +56 -6
- package/packages/gui/src/stores/groove.js +35 -44
- package/packages/gui/src/stores/slices/agents-slice.js +27 -4
- package/packages/gui/src/stores/slices/teams-slice.js +22 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-DTFtRtkx.css +0 -1
- package/packages/gui/dist/assets/index-DTFtRtkx.css +0 -1
|
@@ -369,54 +369,39 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
369
369
|
break;
|
|
370
370
|
}
|
|
371
371
|
|
|
372
|
-
case 'innerchat:
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
icMsgs[ic.id] = ic;
|
|
378
|
-
persistJSON('groove:innerchatMessages', icMsgs);
|
|
379
|
-
return { innerchatMessages: icMsgs };
|
|
380
|
-
});
|
|
381
|
-
// Tag the chat message with innerchat metadata
|
|
382
|
-
set((s) => {
|
|
383
|
-
const history = { ...s.chatHistory };
|
|
384
|
-
const arr = history[ic.to.id] || [];
|
|
385
|
-
if (arr.length > 0) {
|
|
386
|
-
const last = arr[arr.length - 1];
|
|
387
|
-
if (last.from === 'innerchat' && last.text === ic.message) {
|
|
388
|
-
arr[arr.length - 1] = { ...last, innerchat: { messageId: ic.id, fromAgent: ic.from } };
|
|
389
|
-
history[ic.to.id] = arr;
|
|
390
|
-
persistJSON('groove:chatHistory', history);
|
|
391
|
-
return { chatHistory: history };
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
return {};
|
|
395
|
-
});
|
|
396
|
-
break;
|
|
397
|
-
}
|
|
372
|
+
case 'innerchat:turn': {
|
|
373
|
+
const { thread, turn } = msg.data;
|
|
374
|
+
if (turn.status === 'failed') {
|
|
375
|
+
get().addToast('error', `Relay to ${turn.to.name} failed`, turn.error);
|
|
376
|
+
}
|
|
398
377
|
|
|
399
|
-
|
|
400
|
-
const ic = msg.data;
|
|
401
|
-
get().addChatMessage(ic.from.id, 'innerchat', ic.response, false);
|
|
378
|
+
// The turn lands in the recipient's chat, badged with who sent it.
|
|
402
379
|
set((s) => {
|
|
403
|
-
const icMsgs = { ...s.innerchatMessages };
|
|
404
|
-
icMsgs[ic.id] = ic;
|
|
405
|
-
persistJSON('groove:innerchatMessages', icMsgs);
|
|
406
|
-
// Tag the response chat message with innerchat metadata
|
|
407
380
|
const history = { ...s.chatHistory };
|
|
408
|
-
const arr = history[
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
381
|
+
const arr = [...(history[turn.to.id] || [])];
|
|
382
|
+
arr.push({
|
|
383
|
+
from: 'innerchat',
|
|
384
|
+
text: turn.text,
|
|
385
|
+
timestamp: turn.timestamp,
|
|
386
|
+
isQuery: false,
|
|
387
|
+
innerchat: {
|
|
388
|
+
turnId: turn.id,
|
|
389
|
+
threadId: thread.id,
|
|
390
|
+
kind: turn.kind,
|
|
391
|
+
fromAgent: turn.from,
|
|
392
|
+
},
|
|
393
|
+
});
|
|
394
|
+
history[turn.to.id] = arr.slice(-100);
|
|
395
|
+
persistJSON('groove:chatHistory', history);
|
|
396
|
+
|
|
397
|
+
const threads = { ...s.innerchatThreads, [thread.id]: thread };
|
|
398
|
+
persistJSON('groove:innerchatThreads', threads);
|
|
399
|
+
return { chatHistory: history, innerchatThreads: threads };
|
|
418
400
|
});
|
|
419
|
-
|
|
401
|
+
|
|
402
|
+
if (turn.kind === 'reply' && turn.status !== 'failed') {
|
|
403
|
+
get().addToast('info', `${turn.from.name} replied`, `Forwarded to ${turn.to.name}`);
|
|
404
|
+
}
|
|
420
405
|
break;
|
|
421
406
|
}
|
|
422
407
|
|
|
@@ -620,6 +605,12 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
620
605
|
get().fetchTeams();
|
|
621
606
|
break;
|
|
622
607
|
|
|
608
|
+
// Reordering already applied optimistically on the client that did it;
|
|
609
|
+
// this keeps every other window in sync.
|
|
610
|
+
case 'teams:reordered':
|
|
611
|
+
if (msg.teams) set({ teams: msg.teams });
|
|
612
|
+
break;
|
|
613
|
+
|
|
623
614
|
case 'approval:request':
|
|
624
615
|
set((s) => ({ pendingApprovals: [...s.pendingApprovals, msg.data] }));
|
|
625
616
|
break;
|
|
@@ -104,20 +104,43 @@ export const createAgentsSlice = (set, get) => ({
|
|
|
104
104
|
}
|
|
105
105
|
},
|
|
106
106
|
|
|
107
|
+
// Move an agent to another team. Teams are an organizational grouping —
|
|
108
|
+
// this re-labels the agent and does NOT relocate its working directory, so a
|
|
109
|
+
// running process keeps working where it is.
|
|
110
|
+
async moveAgentToTeam(agentId, teamId) {
|
|
111
|
+
const agent = get().agents.find((a) => a.id === agentId);
|
|
112
|
+
if (!agent || agent.teamId === teamId) return;
|
|
113
|
+
const team = get().teams.find((t) => t.id === teamId);
|
|
114
|
+
try {
|
|
115
|
+
await api.patch(`/agents/${encodeURIComponent(agentId)}`, { teamId });
|
|
116
|
+
get().addToast('success', `Moved ${agent.name} → ${team?.name || 'team'}`);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
get().addToast('error', 'Move failed', err.message);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
|
|
107
122
|
// ── InnerChat ─────────────────────────────────────────────
|
|
108
123
|
|
|
109
|
-
|
|
124
|
+
innerchatThreads: loadJSON('groove:innerchatThreads') || {},
|
|
110
125
|
|
|
111
|
-
|
|
126
|
+
// Relay a message from one agent to another. Pass threadId to continue an
|
|
127
|
+
// existing exchange rather than opening a new one.
|
|
128
|
+
async sendInnerChat(fromId, toId, message, threadId = null) {
|
|
112
129
|
try {
|
|
113
|
-
|
|
114
|
-
return msg;
|
|
130
|
+
return await api.post('/innerchat/send', { from: fromId, to: toId, message, threadId });
|
|
115
131
|
} catch (err) {
|
|
116
132
|
get().addToast('error', 'InnerChat failed', err.message);
|
|
117
133
|
throw err;
|
|
118
134
|
}
|
|
119
135
|
},
|
|
120
136
|
|
|
137
|
+
// Threads this agent is a participant in, most recent first.
|
|
138
|
+
innerchatThreadsFor(agentId) {
|
|
139
|
+
return Object.values(get().innerchatThreads)
|
|
140
|
+
.filter((t) => t.participants.some((p) => p.id === agentId))
|
|
141
|
+
.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
142
|
+
},
|
|
143
|
+
|
|
121
144
|
// ── Chat ──────────────────────────────────────────────────
|
|
122
145
|
|
|
123
146
|
addChatMessage(agentId, from, text, isQuery = false, attachments = undefined) {
|
|
@@ -173,6 +173,28 @@ export const createTeamsSlice = (set, get) => ({
|
|
|
173
173
|
}
|
|
174
174
|
},
|
|
175
175
|
|
|
176
|
+
// Move a team to a new index in the sidebar. Applied optimistically so the
|
|
177
|
+
// drop lands instantly, then reconciled with whatever the daemon persisted.
|
|
178
|
+
async reorderTeams(draggedId, targetId) {
|
|
179
|
+
const current = get().teams;
|
|
180
|
+
const from = current.findIndex((t) => t.id === draggedId);
|
|
181
|
+
const to = current.findIndex((t) => t.id === targetId);
|
|
182
|
+
if (from === -1 || to === -1 || from === to) return;
|
|
183
|
+
|
|
184
|
+
const next = [...current];
|
|
185
|
+
const [moved] = next.splice(from, 1);
|
|
186
|
+
next.splice(to, 0, moved);
|
|
187
|
+
set({ teams: next });
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const { teams } = await api.post('/teams/reorder', { orderedIds: next.map((t) => t.id) });
|
|
191
|
+
if (teams) set({ teams });
|
|
192
|
+
} catch (err) {
|
|
193
|
+
set({ teams: current });
|
|
194
|
+
get().addToast('error', 'Failed to reorder teams', err.message);
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
|
|
176
198
|
async promoteTeam(id) {
|
|
177
199
|
try {
|
|
178
200
|
const team = await api.post(`/teams/${encodeURIComponent(id)}/promote`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.184",
|
|
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)",
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
|
|
3
|
+
import { wrapWithRoleReminder } from './process.js';
|
|
4
|
+
import { getProvider } from './providers/index.js';
|
|
5
|
+
|
|
6
|
+
// Reviving a >5M-token claude session has crashed the CLI mid-HTTP-parse
|
|
7
|
+
// (V8 fatal in JsonStringifier) — past this ceiling the rotator's handoff
|
|
8
|
+
// brief sidesteps the resume entirely.
|
|
9
|
+
const SESSION_RESUME_CEILING = 5_000_000;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Deliver a message to an agent, whatever state it's in.
|
|
13
|
+
*
|
|
14
|
+
* This is the single pipe every inbound message travels — user chat from the
|
|
15
|
+
* GUI and InnerChat relays alike. Handles agent-loop delivery, one-shot and
|
|
16
|
+
* non-interactive providers, queueing behind a running task, session resume,
|
|
17
|
+
* and full rotation when the session is too large to revive.
|
|
18
|
+
*
|
|
19
|
+
* Respawn paths mint a NEW agent id. Callers that track the agent afterwards
|
|
20
|
+
* (InnerChat keys response capture on it) must use the returned `agentId`,
|
|
21
|
+
* not the one they passed in.
|
|
22
|
+
*
|
|
23
|
+
* @returns {Promise<{agentId: string, status: string, resumed: boolean, agent: object|null}>}
|
|
24
|
+
*/
|
|
25
|
+
export async function deliverInstruction(daemon, agentId, message, opts = {}) {
|
|
26
|
+
const { recordFeedback = true } = opts;
|
|
27
|
+
|
|
28
|
+
const agent = daemon.registry.get(agentId);
|
|
29
|
+
if (!agent) throw new Error('Agent not found');
|
|
30
|
+
|
|
31
|
+
const finalMessage = message.trim();
|
|
32
|
+
if (!finalMessage) throw new Error('message is required');
|
|
33
|
+
|
|
34
|
+
// Journalist/rotator treat this as a human steering signal — InnerChat
|
|
35
|
+
// relays opt out so agent-to-agent chatter doesn't pollute user feedback.
|
|
36
|
+
if (recordFeedback) {
|
|
37
|
+
if (daemon.journalist) daemon.journalist.recordUserFeedback(agent, finalMessage);
|
|
38
|
+
if (daemon.rotator) daemon.rotator.recordUserMessage(agentId);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const wrappedMessage = wrapWithRoleReminder(agent.role, finalMessage);
|
|
42
|
+
|
|
43
|
+
// Agent loop path — send straight to the running loop.
|
|
44
|
+
if (daemon.processes.hasAgentLoop(agentId)) {
|
|
45
|
+
const sent = await daemon.processes.sendMessage(agentId, wrappedMessage);
|
|
46
|
+
if (sent) {
|
|
47
|
+
daemon.audit.log('agent.chat', { id: agentId });
|
|
48
|
+
return { agentId, status: 'message_sent', resumed: false, agent };
|
|
49
|
+
}
|
|
50
|
+
// Loop exists but isn't running — fall through to resume/rotate.
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const provider = getProvider(agent.provider);
|
|
54
|
+
|
|
55
|
+
// One-shot providers (groove-network): kill any running instance and respawn
|
|
56
|
+
// with the message as --prompt. No handoff brief, no resume, no queue.
|
|
57
|
+
if (provider?.constructor?.isOneShot) {
|
|
58
|
+
const oldConfig = { ...agent };
|
|
59
|
+
if (daemon.processes.isRunning(agentId)) {
|
|
60
|
+
await daemon.processes.kill(agentId);
|
|
61
|
+
}
|
|
62
|
+
daemon.registry.remove(agentId, { silent: true });
|
|
63
|
+
daemon.locks.release(agentId);
|
|
64
|
+
|
|
65
|
+
const newAgent = await respawn(daemon, {
|
|
66
|
+
role: oldConfig.role,
|
|
67
|
+
scope: oldConfig.scope,
|
|
68
|
+
provider: oldConfig.provider,
|
|
69
|
+
model: oldConfig.model,
|
|
70
|
+
prompt: finalMessage,
|
|
71
|
+
permission: oldConfig.permission || 'full',
|
|
72
|
+
workingDir: oldConfig.workingDir,
|
|
73
|
+
name: oldConfig.name,
|
|
74
|
+
teamId: oldConfig.teamId,
|
|
75
|
+
});
|
|
76
|
+
daemon.audit.log('agent.instruct', { id: agentId, newId: newAgent.id, resumed: false });
|
|
77
|
+
return { agentId: newAgent.id, status: 'respawned', resumed: false, agent: newAgent };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Non-interactive CLI providers (e.g. Gemini): one prompt per spawn, cannot
|
|
81
|
+
// resume — respawn preserving the original introContext.
|
|
82
|
+
if (provider?.constructor?.nonInteractive && !daemon.processes.isRunning(agentId)) {
|
|
83
|
+
const oldConfig = { ...agent };
|
|
84
|
+
daemon.registry.remove(agentId, { silent: true });
|
|
85
|
+
daemon.locks.release(agentId);
|
|
86
|
+
|
|
87
|
+
const newAgent = await respawn(daemon, {
|
|
88
|
+
role: oldConfig.role,
|
|
89
|
+
scope: oldConfig.scope,
|
|
90
|
+
provider: oldConfig.provider,
|
|
91
|
+
model: oldConfig.model,
|
|
92
|
+
prompt: finalMessage,
|
|
93
|
+
introContext: oldConfig.introContext,
|
|
94
|
+
permission: oldConfig.permission || 'full',
|
|
95
|
+
workingDir: oldConfig.workingDir,
|
|
96
|
+
name: oldConfig.name,
|
|
97
|
+
teamId: oldConfig.teamId,
|
|
98
|
+
});
|
|
99
|
+
daemon.audit.log('agent.instruct', { id: agentId, newId: newAgent.id, resumed: false });
|
|
100
|
+
return { agentId: newAgent.id, status: 'respawned', resumed: false, agent: newAgent };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Running CLI agent (no loop) — queue behind the current task rather than
|
|
104
|
+
// killing and respawning mid-work.
|
|
105
|
+
if (daemon.processes.isRunning(agentId)) {
|
|
106
|
+
daemon.processes.queueMessage(agentId, wrappedMessage);
|
|
107
|
+
daemon.audit.log('agent.chat.queued', { id: agentId });
|
|
108
|
+
return { agentId, status: 'message_queued', resumed: false, agent };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Stopped CLI agent — resume the session, or rotate past the ceiling.
|
|
112
|
+
const resumed = !!agent.sessionId && (agent.tokensUsed || 0) < SESSION_RESUME_CEILING;
|
|
113
|
+
const newAgent = resumed
|
|
114
|
+
? await daemon.processes.resume(agentId, wrappedMessage)
|
|
115
|
+
: await daemon.rotator.rotate(agentId, { additionalPrompt: wrappedMessage });
|
|
116
|
+
|
|
117
|
+
daemon.audit.log('agent.instruct', { id: agentId, newId: newAgent.id, resumed });
|
|
118
|
+
return { agentId: newAgent.id, status: resumed ? 'resumed' : 'rotated', resumed, agent: newAgent };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Spawn, flushing the registry's pending removals if it fails so a failed
|
|
122
|
+
// respawn doesn't silently erase the agent it was replacing.
|
|
123
|
+
async function respawn(daemon, config) {
|
|
124
|
+
try {
|
|
125
|
+
return await daemon.processes.spawn(config);
|
|
126
|
+
} catch (spawnErr) {
|
|
127
|
+
daemon.registry.flushPendingRemovals();
|
|
128
|
+
throw spawnErr;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -10,7 +10,7 @@ import express from 'express';
|
|
|
10
10
|
import { WebSocketServer } from 'ws';
|
|
11
11
|
import { Registry } from './registry.js';
|
|
12
12
|
import { createApi } from './api.js';
|
|
13
|
-
import { ProcessManager } from './process.js';
|
|
13
|
+
import { ProcessManager, sanitizeFilename } from './process.js';
|
|
14
14
|
import { StateManager } from './state.js';
|
|
15
15
|
import { Introducer } from './introducer.js';
|
|
16
16
|
import { LockManager } from './lockmanager.js';
|
|
@@ -750,7 +750,11 @@ export class Daemon {
|
|
|
750
750
|
try {
|
|
751
751
|
// Build set of agent names still in the registry — never remove their logs
|
|
752
752
|
const allAgents = this.registry.getAll();
|
|
753
|
-
|
|
753
|
+
// Raw logs are keyed by sanitized name; GROOVE_AGENT_LOGS/ subdirectories
|
|
754
|
+
// are keyed by agent id (journalist.js). Comparing either against the
|
|
755
|
+
// wrong set deletes live agents' history.
|
|
756
|
+
const activeNames = new Set(allAgents.map((a) => sanitizeFilename(a.name)));
|
|
757
|
+
const activeIds = new Set(allAgents.map((a) => a.id));
|
|
754
758
|
|
|
755
759
|
// Safety: if registry is empty but log files exist, state may have been
|
|
756
760
|
// lost (corrupt JSON, partial write). Skip log cleanup to prevent
|
|
@@ -778,7 +782,7 @@ export class Daemon {
|
|
|
778
782
|
if (existsSync(agentLogsDir)) {
|
|
779
783
|
for (const dir of readdirSync(agentLogsDir, { withFileTypes: true })) {
|
|
780
784
|
if (!dir.isDirectory()) continue;
|
|
781
|
-
if (
|
|
785
|
+
if (activeIds.has(dir.name)) continue;
|
|
782
786
|
try { rmSync(resolve(agentLogsDir, dir.name), { recursive: true }); cleaned++; } catch { /* skip */ }
|
|
783
787
|
}
|
|
784
788
|
// Remove GROOVE_AGENT_LOGS/ itself if empty
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
|
|
3
|
+
// The agent-facing InnerChat instructions, in one place.
|
|
4
|
+
//
|
|
5
|
+
// These are injected into a new agent's spawn prompt AND written into every
|
|
6
|
+
// AGENTS_REGISTRY.md. Both are needed: the prompt reaches the agent on turn
|
|
7
|
+
// one, and the file survives context compaction — an agent that has forgotten
|
|
8
|
+
// the prompt can re-read the registry it already knows to consult.
|
|
9
|
+
//
|
|
10
|
+
// No imports on purpose — process.js and introducer.js both pull from here.
|
|
11
|
+
|
|
12
|
+
export function innerChatInstructions(port = 31415, agentName = 'YOUR_NAME') {
|
|
13
|
+
return [
|
|
14
|
+
'## Consulting Other Agents (InnerChat)',
|
|
15
|
+
'',
|
|
16
|
+
'Other GROOVE agents may be working alongside you, including on other teams.',
|
|
17
|
+
'When the user asks you to reach out to, coordinate with, or get input from',
|
|
18
|
+
'another agent, use this — it sends them a message and waits for their reply:',
|
|
19
|
+
'',
|
|
20
|
+
'```bash',
|
|
21
|
+
`curl -s http://localhost:${port}/api/innerchat/ask -X POST -H 'Content-Type: application/json' \\`,
|
|
22
|
+
` -d '{"from":"${agentName}","to":"AGENT_NAME","message":"YOUR_QUESTION"}'`,
|
|
23
|
+
'```',
|
|
24
|
+
'',
|
|
25
|
+
'The command BLOCKS until they answer, then returns:',
|
|
26
|
+
'`{"from":"...","reply":"...","exchangesUsed":N,"exchangesRemaining":N}`',
|
|
27
|
+
'',
|
|
28
|
+
'Call it again to continue — prior turns are included automatically, so just say',
|
|
29
|
+
'the next thing. Keep going until you reach a conclusion together, then report',
|
|
30
|
+
'that conclusion to the user.',
|
|
31
|
+
'',
|
|
32
|
+
'**Rules:**',
|
|
33
|
+
'- Only start a conversation when the user asks you to. Never consult other agents on your own initiative.',
|
|
34
|
+
`- \`from\` must be your own agent name${agentName === 'YOUR_NAME' ? ' (it is in your $GROOVE_AGENT_NAME environment variable)' : ''}.`,
|
|
35
|
+
'- Agent names are listed in `AGENTS_REGISTRY.md`, including agents on other teams.',
|
|
36
|
+
'- One question per call. Wait for the answer before deciding what to ask next.',
|
|
37
|
+
'- Read any error you get back — it tells you what to do (unknown name, cap reached, or they are waiting on you).',
|
|
38
|
+
'- `exchangesRemaining` counts down. At 0 the conversation is over: stop and report what you have.',
|
|
39
|
+
'- If another agent asks YOU something, answer it directly in your next message. They are blocked until you do.',
|
|
40
|
+
];
|
|
41
|
+
}
|