neohive 6.2.2 → 6.4.0
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/CHANGELOG.md +26 -0
- package/README.md +3 -0
- package/cli.js +277 -55
- package/dashboard.html +108 -10
- package/dashboard.js +33 -2
- package/package.json +1 -1
- package/server.js +455 -508
- package/tools/governance.js +33 -8
- package/tools/knowledge.js +4 -4
- package/tools/messaging.js +3 -1
- package/tools/safety.js +4 -4
- package/tools/tasks.js +12 -7
- package/tools/workflows.js +2 -1
package/dashboard.html
CHANGED
|
@@ -3,7 +3,19 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Neohive</title>
|
|
6
|
+
<title>Neohive | Multi-Agent Coordination Dashboard</title>
|
|
7
|
+
<meta name="description" content="Neohive — The ultimate multi-agent coordination and management dashboard. Orchestrate, monitor, and scale your AI workforce in real-time.">
|
|
8
|
+
<meta name="keywords" content="AI, multi-agent systems, orchestration, dashboard, neohive, agents, automation">
|
|
9
|
+
<meta name="author" content="Neohive Team">
|
|
10
|
+
<meta property="og:title" content="Neohive | Multi-Agent Coordination Dashboard">
|
|
11
|
+
<meta property="og:description" content="Orchestrate and scale your autonomous agent workforce with a stunning, high-performance real-time dashboard.">
|
|
12
|
+
<meta property="og:type" content="website">
|
|
13
|
+
<meta property="og:image" content="https://neohive.ai/og-image.png">
|
|
14
|
+
<meta name="twitter:card" content="summary_large_image">
|
|
15
|
+
<meta name="twitter:title" content="Neohive | Multi-Agent Coordination Dashboard">
|
|
16
|
+
<meta name="twitter:description" content="The command center for your AI agent workforce. Real-time monitoring and scalable orchestration.">
|
|
17
|
+
<meta name="twitter:image" content="https://neohive.ai/og-image.png">
|
|
18
|
+
<meta name="robots" content="index, follow">
|
|
7
19
|
<link rel="icon" href="favicon.png" type="image/png" sizes="16x16">
|
|
8
20
|
<link rel="icon" href="logo.svg" type="image/svg+xml">
|
|
9
21
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
@@ -183,7 +195,7 @@
|
|
|
183
195
|
}
|
|
184
196
|
|
|
185
197
|
.logo {
|
|
186
|
-
font-size:
|
|
198
|
+
font-size: clamp(18px, 4vw, 22px);
|
|
187
199
|
font-weight: 700;
|
|
188
200
|
letter-spacing: -0.01em;
|
|
189
201
|
color: var(--text);
|
|
@@ -3149,9 +3161,18 @@
|
|
|
3149
3161
|
}
|
|
3150
3162
|
|
|
3151
3163
|
.header { padding: 0 8px; }
|
|
3152
|
-
.logo { font-size:
|
|
3164
|
+
.logo { font-size: clamp(15px, 5vw, 18px); }
|
|
3153
3165
|
.header-left { gap: 6px; }
|
|
3154
3166
|
|
|
3167
|
+
/* Increase touch targets for mobile buttons */
|
|
3168
|
+
.btn, .nh-btn {
|
|
3169
|
+
min-height: 44px;
|
|
3170
|
+
padding: 8px 16px;
|
|
3171
|
+
display: inline-flex;
|
|
3172
|
+
align-items: center;
|
|
3173
|
+
justify-content: center;
|
|
3174
|
+
}
|
|
3175
|
+
|
|
3155
3176
|
/* Compact header buttons */
|
|
3156
3177
|
.header-actions { gap: 2px; }
|
|
3157
3178
|
.phone-btn { padding: 4px 6px; font-size: 13px; }
|
|
@@ -4361,12 +4382,29 @@
|
|
|
4361
4382
|
<!-- Settings dropdown -->
|
|
4362
4383
|
<div style="position:relative;display:inline-block">
|
|
4363
4384
|
<button class="header-settings-btn" onclick="toggleSettingsMenu()" title="Settings">
|
|
4364
|
-
<svg viewBox="0 0 16 16" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.5"><
|
|
4385
|
+
<svg viewBox="0 0 16 16" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6.5 1.5h3l.5 1.5a5 5 0 011.2.7l1.6-.4 1.5 2.6-1.1 1.2a5 5 0 010 1.4l1.1 1.2-1.5 2.6-1.6-.4a5 5 0 01-1.2.7l-.5 1.5h-3l-.5-1.5A5 5 0 014.8 12l-1.6.4L1.7 9.8l1.1-1.2a5 5 0 010-1.4L1.7 6l1.5-2.6 1.6.4a5 5 0 011.2-.7z"/><circle cx="8" cy="8" r="2"/></svg>
|
|
4365
4386
|
</button>
|
|
4366
4387
|
<div id="settings-menu" style="display:none;position:absolute;right:0;top:100%;margin-top:6px;background:var(--surface);border:1px solid var(--border);border-radius:10px;overflow:hidden;z-index:300;min-width:180px;box-shadow:var(--shadow-lg)">
|
|
4367
4388
|
<div class="settings-item" onclick="toggleTheme();toggleSettingsMenu()"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="4"/><path d="M8 1v2M8 13v2M1 8h2M13 8h2"/></svg> Theme</div>
|
|
4368
4389
|
<div class="settings-item" id="settings-notif-item" onclick="toggleCombinedNotifications();"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6a4 4 0 018 0v3l2 2H2l2-2z"/><path d="M6 13a2 2 0 004 0"/></svg> <span id="settings-notif-label">Notifications</span></div>
|
|
4369
4390
|
<div style="height:1px;background:var(--border);margin:4px 8px"></div>
|
|
4391
|
+
<div style="padding:8px 12px;display:flex;align-items:center;gap:8px;font-size:13px;color:var(--text-muted)">
|
|
4392
|
+
<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6"/><path d="M8 5v3l2 2"/></svg>
|
|
4393
|
+
<span style="flex:1;white-space:nowrap">Idle poll (s)</span>
|
|
4394
|
+
<input id="settings-idle-poll-input" type="number" min="10" max="600" step="10" value="90"
|
|
4395
|
+
style="width:60px;background:var(--bg);border:1px solid var(--border);border-radius:5px;color:var(--text);padding:2px 6px;font-size:13px;text-align:right"
|
|
4396
|
+
onclick="event.stopPropagation()"
|
|
4397
|
+
onchange="setIdlePollInterval(this.value)">
|
|
4398
|
+
</div>
|
|
4399
|
+
<div style="padding:8px 12px;display:flex;align-items:center;gap:8px;font-size:13px;color:var(--text-muted)">
|
|
4400
|
+
<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 1v6l3 3"/><circle cx="8" cy="8" r="7"/></svg>
|
|
4401
|
+
<span style="flex:1;white-space:nowrap">Listen timeout (s)</span>
|
|
4402
|
+
<input id="settings-listen-poll-input" type="number" min="30" max="600" step="30" value="120"
|
|
4403
|
+
style="width:60px;background:var(--bg);border:1px solid var(--border);border-radius:5px;color:var(--text);padding:2px 6px;font-size:13px;text-align:right"
|
|
4404
|
+
onclick="event.stopPropagation()"
|
|
4405
|
+
onchange="setListenPollInterval(this.value)">
|
|
4406
|
+
</div>
|
|
4407
|
+
<div style="height:1px;background:var(--border);margin:4px 8px"></div>
|
|
4370
4408
|
<div class="settings-item" onclick="exportShareableHTML();toggleSettingsMenu()">Export HTML</div>
|
|
4371
4409
|
<div class="settings-item" onclick="exportJSON();toggleSettingsMenu()">Export JSON</div>
|
|
4372
4410
|
<div class="settings-item" onclick="enterReplay();toggleSettingsMenu()">Replay</div>
|
|
@@ -9838,11 +9876,11 @@ var ROLE_SKILLS = {
|
|
|
9838
9876
|
monitor: ['observability', 'logging', 'performance', 'health-checks']
|
|
9839
9877
|
};
|
|
9840
9878
|
var ROLE_PROMPTS = {
|
|
9841
|
-
lead: function(name) { return 'You are ' + name + ', the Coordinator in a multi-agent team. Register as "' + name + '".\n\nYour
|
|
9842
|
-
backend: function(name) { return 'You are ' + name + ', a Developer in a multi-agent team. Register as "' + name + '"
|
|
9843
|
-
frontend: function(name) { return 'You are ' + name + ', a Frontend Developer in a multi-agent team. Register as "' + name + '"
|
|
9844
|
-
quality: function(name) { return 'You are ' + name + ', a Reviewer in a multi-agent team. Register as "' + name + '"
|
|
9845
|
-
monitor: function(name) { return 'You are ' + name + ', a System Monitor in a multi-agent team. Register as "' + name + '"
|
|
9879
|
+
lead: function(name) { return 'You are ' + name + ', the Coordinator in a multi-agent team. Register as "' + name + '", call update_profile() to set your role, call get_briefing() for project context, then call listen() to receive the first request.\n\nYour loop:\n1. Receive request via listen()\n2. Break it into subtasks — create_task() per item, assign to agents\n3. Create a workflow with create_workflow() for multi-step plans\n4. Delegate via send_message() to each assigned agent\n5. Monitor with workflow_status() and list_tasks()\n6. Check updates with messages(action="consume") without blocking\n7. Synthesize results and report back to the user\n8. Call listen(outcome="completed", summary="...") for the next task\n\nRules:\n- NEVER edit files or write code — delegate ALL implementation to other agents\n- Always report synthesis back to the user via send_message()\n- If listen() returns retry: true, call listen() again immediately'; },
|
|
9880
|
+
backend: function(name) { return 'You are ' + name + ', a Backend Developer in a multi-agent team. Register as "' + name + '", call update_profile() to set your role, call get_briefing() for project context, then call listen() to wait for tasks.\n\nYour loop:\n1. Receive task via listen()\n2. Call update_task(status="in_progress", task_id=...) to claim it\n3. Call lock_file() before editing any shared file\n4. Implement the changes — clean, production-quality code\n5. Call unlock_file() when done\n6. Call update_task(status="done", task_id=...)\n7. Report to Lead via send_message(): what you did, files changed, decisions made, any blockers\n8. Call listen(outcome="completed", task_id=..., summary="...") for the next task\n\nIf a lock is already held: notify Lead via send_message() and call listen() to wait.\nIf listen() returns retry: true, call listen() again immediately.'; },
|
|
9881
|
+
frontend: function(name) { return 'You are ' + name + ', a Frontend Developer in a multi-agent team. Register as "' + name + '", call update_profile() to set your role, call get_briefing() for project context, then call listen() to wait for tasks.\n\nYour loop:\n1. Receive task via listen()\n2. Call update_task(status="in_progress", task_id=...) to claim it\n3. Call lock_file() before editing any shared frontend file\n4. Implement UI/UX changes — clean, accessible, responsive code\n5. Call unlock_file() when done\n6. Call update_task(status="done", task_id=...)\n7. Report to Lead via send_message(): files changed, design decisions, screenshots if relevant\n8. Call listen(outcome="completed", task_id=..., summary="...") for the next task\n\nIf a lock is already held: notify Lead via send_message() and call listen() to wait.\nIf listen() returns retry: true, call listen() again immediately.'; },
|
|
9882
|
+
quality: function(name) { return 'You are ' + name + ', a Code Reviewer in a multi-agent team. Register as "' + name + '", call update_profile() to set your role, call get_briefing() for project context, then call listen() to wait for review requests.\n\nYour loop:\n1. Receive review request via listen()\n2. Call update_task(status="in_progress", task_id=...) to claim it\n3. Read the actual files that were changed\n4. Check for: bugs, security issues, logic errors, code style, edge cases\n5. Call submit_review(approved=true/false, feedback="...") with structured feedback\n6. Report to Lead via send_message(): blockers vs suggestions with file:line references\n7. Call update_task(status="done", task_id=...)\n8. Call listen(outcome="completed", task_id=..., summary="...") for the next review\n\nBe specific: reference file paths and line numbers. Separate blockers from suggestions.\nIf listen() returns retry: true, call listen() again immediately.'; },
|
|
9883
|
+
monitor: function(name) { return 'You are ' + name + ', a System Monitor in a multi-agent team. Register as "' + name + '", call update_profile() to set your role, call get_briefing() for project context, then call listen() to begin monitoring.\n\nYour loop (runs continuously):\n1. Call list_agents() — flag agents with last_activity > 5 min and status != offline\n2. Call list_tasks() — flag in_progress tasks whose assignee appears idle\n3. Nudge idle agents: send_message() asking them to resume their listen() loop\n4. Reassign stuck tasks (no progress > 10 min): update_task() to reset to pending\n5. For blocked_permanent tasks: send_message() to Lead immediately\n6. Log all interventions via workspace_write()\n7. Call listen(outcome="completed", summary="...") — repeat\n\nEscalation: if unresolved after 2 attempts, create_task() assigned to Lead describing the issue.\nIf listen() returns retry: true, call listen() again immediately.\nNever stop monitoring.'; }
|
|
9846
9884
|
};
|
|
9847
9885
|
|
|
9848
9886
|
function renderLaunchPanel() {
|
|
@@ -10651,7 +10689,11 @@ function copyLaunchPrompt() {
|
|
|
10651
10689
|
|
|
10652
10690
|
function toggleSettingsMenu() {
|
|
10653
10691
|
var menu = document.getElementById('settings-menu');
|
|
10654
|
-
if (menu)
|
|
10692
|
+
if (menu) {
|
|
10693
|
+
var opening = menu.style.display === 'none';
|
|
10694
|
+
menu.style.display = opening ? 'block' : 'none';
|
|
10695
|
+
if (opening) { initIdlePollInput(); initListenPollInput(); }
|
|
10696
|
+
}
|
|
10655
10697
|
}
|
|
10656
10698
|
// Close settings when clicking outside
|
|
10657
10699
|
document.addEventListener('click', function(e) {
|
|
@@ -10947,6 +10989,8 @@ function setCoordinatorMode(mode) {
|
|
|
10947
10989
|
showToast('!', 'Failed: ' + data.error);
|
|
10948
10990
|
} else {
|
|
10949
10991
|
showToast('✓', 'Coordinator mode: ' + (mode === 'responsive' ? 'Stay with me' : 'Run autonomously'));
|
|
10992
|
+
var listenInput = document.getElementById('settings-listen-poll-input');
|
|
10993
|
+
if (listenInput) listenInput.value = mode === 'responsive' ? 120 : 90;
|
|
10950
10994
|
renderOverview();
|
|
10951
10995
|
}
|
|
10952
10996
|
}).catch(function() {
|
|
@@ -10954,6 +10998,60 @@ function setCoordinatorMode(mode) {
|
|
|
10954
10998
|
});
|
|
10955
10999
|
}
|
|
10956
11000
|
|
|
11001
|
+
// ==================== IDLE POLL INTERVAL ====================
|
|
11002
|
+
|
|
11003
|
+
function setIdlePollInterval(value) {
|
|
11004
|
+
var secs = parseInt(value, 10);
|
|
11005
|
+
if (isNaN(secs) || secs < 10) return;
|
|
11006
|
+
lttFetch('/api/config', {
|
|
11007
|
+
method: 'POST',
|
|
11008
|
+
headers: { 'Content-Type': 'application/json' },
|
|
11009
|
+
body: JSON.stringify({ idle_poll_interval: secs })
|
|
11010
|
+
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
11011
|
+
if (data.error) {
|
|
11012
|
+
showToast('!', 'Failed: ' + data.error);
|
|
11013
|
+
} else {
|
|
11014
|
+
showToast('✓', 'Idle poll interval: ' + secs + 's');
|
|
11015
|
+
}
|
|
11016
|
+
}).catch(function() {
|
|
11017
|
+
showToast('!', 'Failed to save idle poll interval');
|
|
11018
|
+
});
|
|
11019
|
+
}
|
|
11020
|
+
|
|
11021
|
+
function initIdlePollInput() {
|
|
11022
|
+
lttFetch('/api/config').then(function(r) { return r.json(); }).then(function(cfg) {
|
|
11023
|
+
var input = document.getElementById('settings-idle-poll-input');
|
|
11024
|
+
if (input && cfg.idle_poll_interval) input.value = cfg.idle_poll_interval;
|
|
11025
|
+
}).catch(function() {});
|
|
11026
|
+
}
|
|
11027
|
+
|
|
11028
|
+
// ==================== LISTEN POLL INTERVAL ====================
|
|
11029
|
+
|
|
11030
|
+
function setListenPollInterval(value) {
|
|
11031
|
+
var secs = parseInt(value, 10);
|
|
11032
|
+
if (isNaN(secs) || secs < 30) return;
|
|
11033
|
+
lttFetch('/api/config', {
|
|
11034
|
+
method: 'POST',
|
|
11035
|
+
headers: { 'Content-Type': 'application/json' },
|
|
11036
|
+
body: JSON.stringify({ listen_poll_interval: secs })
|
|
11037
|
+
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
11038
|
+
if (data.error) {
|
|
11039
|
+
showToast('!', 'Failed: ' + data.error);
|
|
11040
|
+
} else {
|
|
11041
|
+
showToast('✓', 'Listen timeout: ' + secs + 's');
|
|
11042
|
+
}
|
|
11043
|
+
}).catch(function() {
|
|
11044
|
+
showToast('!', 'Failed to save listen timeout');
|
|
11045
|
+
});
|
|
11046
|
+
}
|
|
11047
|
+
|
|
11048
|
+
function initListenPollInput() {
|
|
11049
|
+
lttFetch('/api/config').then(function(r) { return r.json(); }).then(function(cfg) {
|
|
11050
|
+
var input = document.getElementById('settings-listen-poll-input');
|
|
11051
|
+
if (input && cfg.listen_poll_interval) input.value = cfg.listen_poll_interval;
|
|
11052
|
+
}).catch(function() {});
|
|
11053
|
+
}
|
|
11054
|
+
|
|
10957
11055
|
// ==================== TOAST NOTIFICATIONS ====================
|
|
10958
11056
|
|
|
10959
11057
|
var toastQueue = [];
|
package/dashboard.js
CHANGED
|
@@ -175,9 +175,15 @@ function resolveDashboardDefaultDataDir() {
|
|
|
175
175
|
let s = String(envData).trim();
|
|
176
176
|
if (/\$\{workspaceFolder\}/i.test(s)) {
|
|
177
177
|
const root = findCursorProjectRootWithNeohive(process.cwd());
|
|
178
|
-
if (root)
|
|
178
|
+
if (!root) {
|
|
179
|
+
// Placeholder can't be expanded — fall through to the config/walk/cwd strategies
|
|
180
|
+
} else {
|
|
181
|
+
s = s.replace(/\$\{workspaceFolder\}/gi, root);
|
|
182
|
+
return { path: path.resolve(s), source: 'environment' };
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
return { path: path.resolve(s), source: 'environment' };
|
|
179
186
|
}
|
|
180
|
-
return { path: path.resolve(s), source: 'environment' };
|
|
181
187
|
}
|
|
182
188
|
|
|
183
189
|
// 2. Project MCP config — authoritative, written by `neohive init`
|
|
@@ -2733,6 +2739,31 @@ const server = http.createServer(async (req, res) => {
|
|
|
2733
2739
|
res.end(JSON.stringify({ error: 'Failed to set coordinator mode: ' + e.message }));
|
|
2734
2740
|
}
|
|
2735
2741
|
}
|
|
2742
|
+
else if (url.pathname === '/api/config' && req.method === 'GET') {
|
|
2743
|
+
const projectPath = url.searchParams.get('project') || null;
|
|
2744
|
+
const config = readJson(filePath('config.json', projectPath));
|
|
2745
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2746
|
+
res.end(JSON.stringify(config));
|
|
2747
|
+
}
|
|
2748
|
+
else if (url.pathname === '/api/config' && req.method === 'POST') {
|
|
2749
|
+
try {
|
|
2750
|
+
const body = await parseBody(req).catch(() => ({}));
|
|
2751
|
+
const projectPath = url.searchParams.get('project') || null;
|
|
2752
|
+
const dataDir = resolveDataDir(projectPath);
|
|
2753
|
+
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
|
|
2754
|
+
const configFile = filePath('config.json', projectPath);
|
|
2755
|
+
await withFileLock(configFile, () => {
|
|
2756
|
+
const config = readJson(configFile);
|
|
2757
|
+
Object.assign(config, body);
|
|
2758
|
+
fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
|
|
2759
|
+
});
|
|
2760
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2761
|
+
res.end(JSON.stringify({ success: true }));
|
|
2762
|
+
} catch (e) {
|
|
2763
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
2764
|
+
res.end(JSON.stringify({ error: 'Failed to save config: ' + e.message }));
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2736
2767
|
else if (url.pathname === '/api/reset' && req.method === 'POST') {
|
|
2737
2768
|
const body = await parseBody(req).catch(() => ({}));
|
|
2738
2769
|
if (!body.confirm) {
|