neohive 6.4.0 → 6.4.2
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/README.md +109 -9
- package/SECURITY.md +8 -53
- package/cli.js +120 -15
- package/dashboard.html +11 -23
- package/dashboard.js +3 -3
- package/lib/audit.js +9 -7
- package/neohive-plugin/.claude-plugin/plugin.json +24 -0
- package/neohive-plugin/.mcp.json +11 -0
- package/neohive-plugin/README.md +55 -0
- package/neohive-plugin/agents/coordinator.md +27 -0
- package/neohive-plugin/gemini-extension/GEMINI.md +24 -0
- package/neohive-plugin/gemini-extension/settings-snippet.json +12 -0
- package/neohive-plugin/hooks/hooks.json +87 -0
- package/neohive-plugin/scripts/auto-register.sh +48 -0
- package/neohive-plugin/scripts/before-prompt.sh +47 -0
- package/neohive-plugin/scripts/enforce-listen.sh +72 -0
- package/neohive-plugin/scripts/enforce-locks.sh +34 -0
- package/neohive-plugin/scripts/post-tool-use.sh +119 -0
- package/neohive-plugin/scripts/track-activity.sh +23 -0
- package/neohive-plugin/skills/conventions/SKILL.md +30 -0
- package/neohive-plugin/skills/launch-team/SKILL.md +24 -0
- package/neohive-plugin/skills/plan/SKILL.md +21 -0
- package/neohive-plugin/skills/send/SKILL.md +14 -0
- package/neohive-plugin/skills/status/SKILL.md +17 -0
- package/package.json +2 -1
- package/server.js +23 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neohive",
|
|
3
|
-
"version": "6.4.
|
|
3
|
+
"version": "6.4.2",
|
|
4
4
|
"description": "The MCP collaboration layer for AI CLI tools. Turn Claude Code, Gemini CLI, and Codex CLI into a team.",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"bin": {
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"design-system.css",
|
|
25
25
|
"design-system.html",
|
|
26
26
|
"cli.js",
|
|
27
|
+
"neohive-plugin/",
|
|
27
28
|
"lib/",
|
|
28
29
|
"tools/",
|
|
29
30
|
"templates/",
|
package/server.js
CHANGED
|
@@ -1183,7 +1183,7 @@ function buildGuide(level = 'standard') {
|
|
|
1183
1183
|
} else {
|
|
1184
1184
|
rules.push('ROLE: Managed agent. The manager controls your turn.');
|
|
1185
1185
|
rules.push('LOOP: listen() → receive work → update_task(id, "in_progress") → do work → update_task(id, "done") → send_message(manager, summary) → listen(). Never stop.');
|
|
1186
|
-
rules.push('Never call get_work() or
|
|
1186
|
+
rules.push('Never call get_work() or messages() in managed mode.');
|
|
1187
1187
|
}
|
|
1188
1188
|
rules.push('Keep messages short (2-3 paragraphs). Report what you did and what files changed.');
|
|
1189
1189
|
}
|
|
@@ -1260,16 +1260,16 @@ function buildGuide(level = 'standard') {
|
|
|
1260
1260
|
if (isLeadRole) {
|
|
1261
1261
|
const coordinatorMode = getConfig().coordinator_mode || 'responsive';
|
|
1262
1262
|
if (coordinatorMode === 'responsive') {
|
|
1263
|
-
rules.push('COORDINATOR: Use
|
|
1263
|
+
rules.push('COORDINATOR: Use messages(action="check") to check updates non-blockingly. Do NOT block in listen() — stay responsive to the user.');
|
|
1264
1264
|
} else {
|
|
1265
1265
|
rules.push('COORDINATOR: Use listen() to wait for agent results. Only return to human when all tasks are done or blocked.');
|
|
1266
1266
|
}
|
|
1267
1267
|
rules.push('Coordinators do NOT edit files or write code. Delegate ALL code work to other agents.');
|
|
1268
1268
|
}
|
|
1269
1269
|
|
|
1270
|
-
const listenCmd = isManagedMode() ? 'listen()' : (mode === 'group' ? '
|
|
1270
|
+
const listenCmd = isManagedMode() ? 'listen()' : (mode === 'group' ? 'listen(mode="group")' : 'listen()');
|
|
1271
1271
|
if (!isLeadRole) {
|
|
1272
|
-
rules.push(`After EVERY action, call ${listenCmd}. Never use sleep() or poll with
|
|
1272
|
+
rules.push(`After EVERY action, call ${listenCmd}. Never use sleep() or poll with messages().`);
|
|
1273
1273
|
}
|
|
1274
1274
|
|
|
1275
1275
|
if (level === 'minimal') {
|
|
@@ -1570,7 +1570,7 @@ function toolRegister(name, provider = null, skills = null) {
|
|
|
1570
1570
|
const coordinatorMode = getConfig().coordinator_mode || 'responsive';
|
|
1571
1571
|
nextAction = coordinatorMode === 'autonomous'
|
|
1572
1572
|
? 'Call get_briefing() to load project context, then listen() to coordinate your team.'
|
|
1573
|
-
: 'Call get_briefing() to load project context, then
|
|
1573
|
+
: 'Call get_briefing() to load project context, then messages(action="check") to check for pending work.';
|
|
1574
1574
|
}
|
|
1575
1575
|
|
|
1576
1576
|
// --- Build the result: next_action FIRST, then context ---
|
|
@@ -1695,7 +1695,7 @@ async function toolSendMessage(content, to = null, reply_to = null, channel = nu
|
|
|
1695
1695
|
const effectiveSendLimit = isAutonomousMode() ? 5 : sendLimit;
|
|
1696
1696
|
const myRole = (getProfiles()[registeredName] || {}).role;
|
|
1697
1697
|
if (isGroupMode() && sendsSinceLastListen >= effectiveSendLimit && myRole !== 'Coordinator') {
|
|
1698
|
-
return { error: `You must call
|
|
1698
|
+
return { error: `You must call listen() before sending again. You've sent ${sendsSinceLastListen} message(s) without listening (limit: ${effectiveSendLimit}). This prevents message storms.` };
|
|
1699
1699
|
}
|
|
1700
1700
|
|
|
1701
1701
|
// Response budget: track unaddressed sends, hint when depleted
|
|
@@ -1993,7 +1993,7 @@ async function toolSendMessage(content, to = null, reply_to = null, channel = nu
|
|
|
1993
1993
|
if (!recipientAlive) {
|
|
1994
1994
|
result.warning = `Agent "${to}" appears offline (PID not running). Message queued but may not be received until they reconnect.`;
|
|
1995
1995
|
} else if (to !== '__user__' && agents[to] && !agents[to].listening_since) {
|
|
1996
|
-
result.note = `Agent "${to}" is currently working (not in listen mode). Message queued — they'll see it when they finish their current task and call
|
|
1996
|
+
result.note = `Agent "${to}" is currently working (not in listen mode). Message queued — they'll see it when they finish their current task and call listen().`;
|
|
1997
1997
|
}
|
|
1998
1998
|
|
|
1999
1999
|
// Coordinator enforcement: warn if sending work assignment without creating a task first
|
|
@@ -2036,7 +2036,7 @@ function toolBroadcast(content) {
|
|
|
2036
2036
|
const effectiveSendLimitBcast = isAutonomousMode() ? 5 : sendLimit;
|
|
2037
2037
|
const myRole = (getProfiles()[registeredName] || {}).role;
|
|
2038
2038
|
if (isGroupMode() && sendsSinceLastListen >= effectiveSendLimitBcast && myRole !== 'Coordinator') {
|
|
2039
|
-
return { error: `You must call
|
|
2039
|
+
return { error: `You must call listen() before broadcasting again. You've sent ${sendsSinceLastListen} message(s) without listening (limit: ${effectiveSendLimitBcast}).` };
|
|
2040
2040
|
}
|
|
2041
2041
|
|
|
2042
2042
|
const rateErr = checkRateLimit(content, '__broadcast__');
|
|
@@ -2355,10 +2355,10 @@ async function toolListenCodex(from = null, outcome = null, task_id = null, summ
|
|
|
2355
2355
|
const taskList = getTasks();
|
|
2356
2356
|
const task = taskList.find(t => t.id === task_id);
|
|
2357
2357
|
if (!task) {
|
|
2358
|
-
return { error: true, message: `Invalid task_id "${task_id}" — task does not exist. Check list_tasks() and call
|
|
2358
|
+
return { error: true, message: `Invalid task_id "${task_id}" — task does not exist. Check list_tasks() and call listen(mode="codex") again with the correct task_id.` };
|
|
2359
2359
|
}
|
|
2360
2360
|
if (task.assignee && task.assignee !== registeredName) {
|
|
2361
|
-
return { error: true, message: `Task "${task_id}" is assigned to ${task.assignee}, not to you (${registeredName}). You cannot update another agent's task via
|
|
2361
|
+
return { error: true, message: `Task "${task_id}" is assigned to ${task.assignee}, not to you (${registeredName}). You cannot update another agent's task via listen(mode="codex").` };
|
|
2362
2362
|
}
|
|
2363
2363
|
const statusMap = { completed: 'done', blocked: 'blocked', failed: 'blocked_permanent' };
|
|
2364
2364
|
const newStatus = statusMap[outcome];
|
|
@@ -2486,9 +2486,9 @@ function toolSetConversationMode(mode) {
|
|
|
2486
2486
|
}
|
|
2487
2487
|
|
|
2488
2488
|
const messages = {
|
|
2489
|
-
group: 'Group mode enabled. Use
|
|
2489
|
+
group: 'Group mode enabled. Use listen(mode="group") to receive batched messages. All messages are shared with everyone.',
|
|
2490
2490
|
direct: 'Direct mode enabled. Use listen() for point-to-point messaging.',
|
|
2491
|
-
managed: 'Managed mode enabled. Call claim_manager() to become the manager, or wait for the manager to give you the floor via yield_floor(). Use listen()
|
|
2491
|
+
managed: 'Managed mode enabled. Call claim_manager() to become the manager, or wait for the manager to give you the floor via yield_floor(). Use listen() to receive messages.',
|
|
2492
2492
|
};
|
|
2493
2493
|
return { success: true, mode, message: messages[mode] };
|
|
2494
2494
|
}
|
|
@@ -2649,10 +2649,10 @@ async function toolListenGroup(outcome = null, task_id = null, summary = null) {
|
|
|
2649
2649
|
const taskList = getTasks();
|
|
2650
2650
|
const task = taskList.find(t => t.id === task_id);
|
|
2651
2651
|
if (!task) {
|
|
2652
|
-
return { error: true, message: `Invalid task_id "${task_id}" — task does not exist. Check list_tasks() and call
|
|
2652
|
+
return { error: true, message: `Invalid task_id "${task_id}" — task does not exist. Check list_tasks() and call listen() again with the correct task_id.` };
|
|
2653
2653
|
}
|
|
2654
2654
|
if (task.assignee && task.assignee !== registeredName) {
|
|
2655
|
-
return { error: true, message: `Task "${task_id}" is assigned to ${task.assignee}, not to you (${registeredName}). You cannot update another agent's task via
|
|
2655
|
+
return { error: true, message: `Task "${task_id}" is assigned to ${task.assignee}, not to you (${registeredName}). You cannot update another agent's task via listen().` };
|
|
2656
2656
|
}
|
|
2657
2657
|
const statusMap = { completed: 'done', blocked: 'blocked', failed: 'blocked_permanent' };
|
|
2658
2658
|
const newStatus = statusMap[outcome];
|
|
@@ -2906,8 +2906,8 @@ function classifyPriority(msg) {
|
|
|
2906
2906
|
return 'normal';
|
|
2907
2907
|
}
|
|
2908
2908
|
|
|
2909
|
-
// Build the response for
|
|
2910
|
-
// Context/history removed: agents should call
|
|
2909
|
+
// Build the response for listen (group mode) — kept lean to reduce context accumulation
|
|
2910
|
+
// Context/history removed: agents should call messages(action="history") when they need it
|
|
2911
2911
|
function buildListenGroupResponse(batch, consumed, agentName, listenStart) {
|
|
2912
2912
|
saveConsumedIds(agentName, consumed);
|
|
2913
2913
|
touchActivity();
|
|
@@ -5465,7 +5465,7 @@ function toolStartPlan(params) {
|
|
|
5465
5465
|
broadcastSystemMessage(
|
|
5466
5466
|
`[PLAN LAUNCHED] "${name}" — ${steps.length} steps, autonomous mode, ${useParallel ? 'parallel' : 'sequential'}. ` +
|
|
5467
5467
|
`${startedSteps.length} step(s) started. ` +
|
|
5468
|
-
`All agents: call get_work() to enter the autonomous work loop. Do NOT call
|
|
5468
|
+
`All agents: call get_work() to enter the autonomous work loop. Do NOT call listen().`
|
|
5469
5469
|
);
|
|
5470
5470
|
|
|
5471
5471
|
touchActivity();
|
|
@@ -5886,7 +5886,7 @@ function triggerStandupIfDue() {
|
|
|
5886
5886
|
if (inProgress.length > 0) summary += ` In progress: ${inProgress.map(t => `"${t.title}" (${t.assignee || '?'})`).join(', ')}.`;
|
|
5887
5887
|
if (blocked.length > 0) summary += ` BLOCKED: ${blocked.map(t => `"${t.title}" (${t.assignee || '?'})`).join(', ')}.`;
|
|
5888
5888
|
if (recentDone.length > 0) summary += ` Recently done: ${recentDone.length} task(s).`;
|
|
5889
|
-
summary += ' Each agent: report what you did, what\'s blocked, what\'s next. Then call
|
|
5889
|
+
summary += ' Each agent: report what you did, what\'s blocked, what\'s next. Then call listen().';
|
|
5890
5890
|
|
|
5891
5891
|
broadcastSystemMessage(summary, registeredName);
|
|
5892
5892
|
} catch (e) { log.warn("standup trigger failed:", e.message); }
|
|
@@ -6180,7 +6180,7 @@ function toolGetGuide(level = 'standard') {
|
|
|
6180
6180
|
const guide = buildGuide(level);
|
|
6181
6181
|
guide.your_name = registeredName;
|
|
6182
6182
|
if (level !== 'minimal') {
|
|
6183
|
-
guide.workflow = '1. get_briefing → 2. list_tasks/suggest_task → 3. claim task → 4. lock_file → 5. work → 6. unlock_file → 7. update_task done → 8.
|
|
6183
|
+
guide.workflow = '1. get_briefing → 2. list_tasks/suggest_task → 3. claim task → 4. lock_file → 5. work → 6. unlock_file → 7. update_task done → 8. listen()';
|
|
6184
6184
|
}
|
|
6185
6185
|
return guide;
|
|
6186
6186
|
}
|
|
@@ -6498,7 +6498,7 @@ function toolSubmitReview(reviewId, status, feedback) {
|
|
|
6498
6498
|
|
|
6499
6499
|
review.status = status;
|
|
6500
6500
|
review.reviewer = registeredName;
|
|
6501
|
-
review.feedback =
|
|
6501
|
+
review.feedback = feedback || '';
|
|
6502
6502
|
review.reviewed_at = new Date().toISOString();
|
|
6503
6503
|
|
|
6504
6504
|
// Review → retry loop: track review rounds, auto-route feedback, auto-approve after 2 rounds
|
|
@@ -7191,7 +7191,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
7191
7191
|
tools: [
|
|
7192
7192
|
{
|
|
7193
7193
|
name: 'register',
|
|
7194
|
-
description: 'Register this agent\'s identity. Must be called first. Returns a collaboration guide with all tool categories, critical rules, and workflow patterns — READ IT CAREFULLY before doing anything else. Then call get_briefing() for project context, then
|
|
7194
|
+
description: 'Register this agent\'s identity. Must be called first. Returns a collaboration guide with all tool categories, critical rules, and workflow patterns — READ IT CAREFULLY before doing anything else. Then call get_briefing() for project context, then listen() to join the conversation.',
|
|
7195
7195
|
inputSchema: {
|
|
7196
7196
|
type: 'object',
|
|
7197
7197
|
properties: {
|
|
@@ -7289,7 +7289,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
7289
7289
|
},
|
|
7290
7290
|
{
|
|
7291
7291
|
name: 'listen',
|
|
7292
|
-
description: 'Listen for messages. Use mode="standard" (default, direct 1:1), mode="group" (group/managed conversation, batched), or mode="codex" (Codex CLI — returns after 90s). Auto-detects mode from conversation state when mode is omitted.
|
|
7292
|
+
description: 'Listen for messages. Use mode="standard" (default, direct 1:1), mode="group" (group/managed conversation, batched), or mode="codex" (Codex CLI — returns after 90s). Auto-detects mode from conversation state when mode is omitted.',
|
|
7293
7293
|
inputSchema: {
|
|
7294
7294
|
type: 'object',
|
|
7295
7295
|
properties: {
|
|
@@ -7901,7 +7901,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
7901
7901
|
try {
|
|
7902
7902
|
const pending = getUnconsumedMessages(registeredName);
|
|
7903
7903
|
const pendingHint = pending.length > 0
|
|
7904
|
-
? `${pending.length} agent update(s) waiting. Call
|
|
7904
|
+
? `${pending.length} agent update(s) waiting. Call messages(action="consume") to read them.`
|
|
7905
7905
|
: null;
|
|
7906
7906
|
if (!na || bareListenRe.test(na)) {
|
|
7907
7907
|
// No guidance or bare listen() — replace with coordinator hint or nothing
|