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/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: 20px;
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: 17px; }
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"><circle cx="8" cy="8" r="2.5"/><path d="M8 1v2M8 13v2M1 8h2M13 8h2M2.9 2.9l1.4 1.4M11.7 11.7l1.4 1.4M13.1 2.9l-1.4 1.4M4.3 11.7l-1.4 1.4"/></svg>
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 job is to:\n1. Break the user\'s request into tasks and delegate to team agents via send_message\n2. Use create_task() and create_workflow() to formally track work\n3. Monitor progress with workflow_status() and list_tasks()\n4. Use consume_messages() to check agent updates without blocking\n5. Synthesize results and present to the user\n\nYou MUST NOT edit files or write code. Delegate ALL code work to other agents. Your tools: send_message, create_task, create_workflow, advance_workflow, workflow_status, list_tasks, consume_messages, broadcast.'; },
9842
- backend: function(name) { return 'You are ' + name + ', a Developer in a multi-agent team. Register as "' + name + '". Call listen() to wait for tasks.\n\nWhen you receive a task:\n1. Use lock_file() before editing shared code\n2. Implement the requested changes with clean, tested code\n3. Use unlock_file() when done editing\n4. Update your task with update_task(status="done")\n5. Send a summary to the Coordinator with file paths and key decisions\n6. Call listen() again for the next task\n\nFocus on production-quality code. Include file paths in reports.'; },
9843
- frontend: function(name) { return 'You are ' + name + ', a Frontend Developer in a multi-agent team. Register as "' + name + '". Call listen() to wait for tasks.\n\nWhen you receive a task:\n1. Use lock_file() before editing shared frontend code\n2. Implement UI/UX changes following design conventions\n3. Use unlock_file() when done\n4. Update your task with update_task(status="done")\n5. Send a summary to the Coordinator with file paths and screenshots if relevant\n6. Call listen() again for the next task\n\nFocus on clean UI, accessibility, and responsive design.'; },
9844
- quality: function(name) { return 'You are ' + name + ', a Reviewer in a multi-agent team. Register as "' + name + '". Call listen() to wait for review requests.\n\nWhen you receive work to review:\n1. Read the actual files that were changed\n2. Check for bugs, security issues, code style, edge cases\n3. Use submit_review() to formally approve or request changes\n4. Send structured feedback: blockers vs suggestions\n5. Call listen() again\n\nBe constructive and specific. Reference line numbers. Never let mediocre work pass.'; },
9845
- monitor: function(name) { return 'You are ' + name + ', a System Monitor in a multi-agent team. Register as "' + name + '". Call listen() to wait for events.\n\nYour job:\n1. Watch for idle agents, stuck tasks, and circular escalations\n2. Use send_message to nudge idle agents\n3. Use update_task to reassign stuck tasks\n4. Log interventions to your workspace via workspace_write\n5. Call listen() again\n\nNever stop monitoring. You ARE the system intelligence.'; }
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) menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
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('&#x2713;', '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('&#x2713;', '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('&#x2713;', '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) s = s.replace(/\$\{workspaceFolder\}/gi, 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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neohive",
3
- "version": "6.2.2",
3
+ "version": "6.4.0",
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": {