neohive 6.3.0 → 6.4.1
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 +110 -6
- package/cli.js +391 -64
- package/dashboard.html +107 -21
- package/dashboard.js +36 -5
- package/package.json +1 -1
- package/server.js +185 -147
- package/tools/governance.js +28 -6
- package/tools/messaging.js +2 -0
- package/tools/safety.js +3 -3
- package/tools/tasks.js +5 -5
package/dashboard.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
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
7
|
<link rel="icon" href="favicon.png" type="image/png" sizes="16x16">
|
|
8
8
|
<link rel="icon" href="logo.svg" type="image/svg+xml">
|
|
9
9
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
@@ -183,7 +183,7 @@
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
.logo {
|
|
186
|
-
font-size:
|
|
186
|
+
font-size: clamp(18px, 4vw, 22px);
|
|
187
187
|
font-weight: 700;
|
|
188
188
|
letter-spacing: -0.01em;
|
|
189
189
|
color: var(--text);
|
|
@@ -3149,9 +3149,18 @@
|
|
|
3149
3149
|
}
|
|
3150
3150
|
|
|
3151
3151
|
.header { padding: 0 8px; }
|
|
3152
|
-
.logo { font-size:
|
|
3152
|
+
.logo { font-size: clamp(15px, 5vw, 18px); }
|
|
3153
3153
|
.header-left { gap: 6px; }
|
|
3154
3154
|
|
|
3155
|
+
/* Increase touch targets for mobile buttons */
|
|
3156
|
+
.btn, .nh-btn {
|
|
3157
|
+
min-height: 44px;
|
|
3158
|
+
padding: 8px 16px;
|
|
3159
|
+
display: inline-flex;
|
|
3160
|
+
align-items: center;
|
|
3161
|
+
justify-content: center;
|
|
3162
|
+
}
|
|
3163
|
+
|
|
3155
3164
|
/* Compact header buttons */
|
|
3156
3165
|
.header-actions { gap: 2px; }
|
|
3157
3166
|
.phone-btn { padding: 4px 6px; font-size: 13px; }
|
|
@@ -4361,12 +4370,29 @@
|
|
|
4361
4370
|
<!-- Settings dropdown -->
|
|
4362
4371
|
<div style="position:relative;display:inline-block">
|
|
4363
4372
|
<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"><
|
|
4373
|
+
<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
4374
|
</button>
|
|
4366
4375
|
<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
4376
|
<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
4377
|
<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
4378
|
<div style="height:1px;background:var(--border);margin:4px 8px"></div>
|
|
4379
|
+
<div style="padding:8px 12px;display:flex;align-items:center;gap:8px;font-size:13px;color:var(--text-muted)">
|
|
4380
|
+
<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>
|
|
4381
|
+
<span style="flex:1;white-space:nowrap">Idle poll (s)</span>
|
|
4382
|
+
<input id="settings-idle-poll-input" type="number" min="10" max="600" step="10" value="90"
|
|
4383
|
+
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"
|
|
4384
|
+
onclick="event.stopPropagation()"
|
|
4385
|
+
onchange="setIdlePollInterval(this.value)">
|
|
4386
|
+
</div>
|
|
4387
|
+
<div style="padding:8px 12px;display:flex;align-items:center;gap:8px;font-size:13px;color:var(--text-muted)">
|
|
4388
|
+
<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>
|
|
4389
|
+
<span style="flex:1;white-space:nowrap">Listen timeout (s)</span>
|
|
4390
|
+
<input id="settings-listen-poll-input" type="number" min="30" max="600" step="30" value="120"
|
|
4391
|
+
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"
|
|
4392
|
+
onclick="event.stopPropagation()"
|
|
4393
|
+
onchange="setListenPollInterval(this.value)">
|
|
4394
|
+
</div>
|
|
4395
|
+
<div style="height:1px;background:var(--border);margin:4px 8px"></div>
|
|
4370
4396
|
<div class="settings-item" onclick="exportShareableHTML();toggleSettingsMenu()">Export HTML</div>
|
|
4371
4397
|
<div class="settings-item" onclick="exportJSON();toggleSettingsMenu()">Export JSON</div>
|
|
4372
4398
|
<div class="settings-item" onclick="enterReplay();toggleSettingsMenu()">Replay</div>
|
|
@@ -4565,7 +4591,7 @@
|
|
|
4565
4591
|
</div>
|
|
4566
4592
|
</div>
|
|
4567
4593
|
<div class="app-footer">
|
|
4568
|
-
<span>Neohive v6.
|
|
4594
|
+
<span>Neohive v6.4.1</span>
|
|
4569
4595
|
</div>
|
|
4570
4596
|
<div class="profile-popup" id="profile-popup" onclick="event.stopPropagation()">
|
|
4571
4597
|
<div class="profile-popup-header">
|
|
@@ -5256,10 +5282,10 @@ function respawnAgent(agentName) {
|
|
|
5256
5282
|
}
|
|
5257
5283
|
|
|
5258
5284
|
function generateFallbackRespawnPrompt(agentName) {
|
|
5259
|
-
return 'Register as \'' + agentName + '\', then call get_briefing() and
|
|
5285
|
+
return 'Register as \'' + agentName + '\', then call get_briefing() and listen() to rejoin the conversation. ' +
|
|
5260
5286
|
'You are resuming a previous session — call get_compressed_history() to catch up on what you missed. ' +
|
|
5261
5287
|
'Check your workspace with workspace_read() for any saved state. ' +
|
|
5262
|
-
'Then call
|
|
5288
|
+
'Then call listen() and respond to any pending messages.';
|
|
5263
5289
|
}
|
|
5264
5290
|
|
|
5265
5291
|
function showRespawnModal(agentName, prompt) {
|
|
@@ -5465,7 +5491,7 @@ function renderMessages(messages) {
|
|
|
5465
5491
|
} else {
|
|
5466
5492
|
el.innerHTML = '<div class="empty-state">' +
|
|
5467
5493
|
'<div class="empty-icon" style="font-size:40px;opacity:0.2">--</div>' +
|
|
5468
|
-
'<div class="empty-text">Neohive v6.
|
|
5494
|
+
'<div class="empty-text">Neohive v6.4</div>' +
|
|
5469
5495
|
'<div class="empty-sub">Autonomous AI agent teams — one command, zero babysitting</div>' +
|
|
5470
5496
|
'<div class="onboard-steps">' +
|
|
5471
5497
|
'<div class="onboard-step" style="margin-bottom:12px"><span class="onboard-num" style="background:var(--accent)"></span><span style="font-weight:600">Quickest start — one command:</span></div>' +
|
|
@@ -9838,11 +9864,11 @@ var ROLE_SKILLS = {
|
|
|
9838
9864
|
monitor: ['observability', 'logging', 'performance', 'health-checks']
|
|
9839
9865
|
};
|
|
9840
9866
|
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 + '"
|
|
9867
|
+
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'; },
|
|
9868
|
+
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.'; },
|
|
9869
|
+
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.'; },
|
|
9870
|
+
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.'; },
|
|
9871
|
+
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
9872
|
};
|
|
9847
9873
|
|
|
9848
9874
|
function renderLaunchPanel() {
|
|
@@ -10275,7 +10301,7 @@ function doLaunch() {
|
|
|
10275
10301
|
}
|
|
10276
10302
|
|
|
10277
10303
|
// Use generated prompt if available, otherwise build a default
|
|
10278
|
-
var launchPrompt = window._generatedLaunchPrompt || 'You are agent "' + agentName + '". Use the register tool to register as "' + agentName + '", then use
|
|
10304
|
+
var launchPrompt = window._generatedLaunchPrompt || 'You are agent "' + agentName + '". Use the register tool to register as "' + agentName + '", then use listen() to join the conversation.';
|
|
10279
10305
|
navigator.clipboard.writeText(launchPrompt).catch(function() {});
|
|
10280
10306
|
selectedCli = cli;
|
|
10281
10307
|
|
|
@@ -10366,7 +10392,7 @@ function doLaunchAll() {
|
|
|
10366
10392
|
w.state = 'launching';
|
|
10367
10393
|
renderLaunchStatusTracker();
|
|
10368
10394
|
|
|
10369
|
-
var agentPrompt = w.prompt || 'You are agent "' + w.name + '". Use the register tool to register as "' + w.name + '", then use
|
|
10395
|
+
var agentPrompt = w.prompt || 'You are agent "' + w.name + '". Use the register tool to register as "' + w.name + '", then use listen() to join the conversation.';
|
|
10370
10396
|
|
|
10371
10397
|
lttFetch('/api/launch', {
|
|
10372
10398
|
method: 'POST',
|
|
@@ -10495,7 +10521,7 @@ function renderDocs() {
|
|
|
10495
10521
|
el.innerHTML =
|
|
10496
10522
|
'<div class="docs-container">' +
|
|
10497
10523
|
|
|
10498
|
-
'<h2>Neohive v6.
|
|
10524
|
+
'<h2>Neohive v6.4</h2>' +
|
|
10499
10525
|
'<p class="docs-subtitle">True Autonomy Engine \u2014 AI agents that self-organize, self-verify, and never stop working. Works with Claude Code, Gemini CLI, Codex CLI, and Cursor IDE.</p>' +
|
|
10500
10526
|
|
|
10501
10527
|
// Quick Start — One Command
|
|
@@ -10533,7 +10559,7 @@ function renderDocs() {
|
|
|
10533
10559
|
'<p>Opens this web dashboard at <code>http://localhost:3777</code>. You can watch agents chat in real-time, send them messages, and manage your team.</p>' +
|
|
10534
10560
|
'<h4>3. Start Your Agents</h4>' +
|
|
10535
10561
|
'<p>Open two or more terminal windows in your project folder. In each one, start your AI CLI (e.g. type <code>claude</code>) and tell it to register:</p>' +
|
|
10536
|
-
'<pre><code># Terminal 1\nRegister as "Alice" and use
|
|
10562
|
+
'<pre><code># Terminal 1\nRegister as "Alice" and use listen() to join the conversation.\n\n# Terminal 2\nRegister as "Bob" and use listen() to join the conversation.</code></pre>' +
|
|
10537
10563
|
'<p>That\'s it! Your agents can now talk to each other. Or use the <strong>Launch</strong> tab to do this with one click.</p>' +
|
|
10538
10564
|
'</div>' +
|
|
10539
10565
|
|
|
@@ -10617,7 +10643,7 @@ function renderDocs() {
|
|
|
10617
10643
|
'<h4>Can I run multiple projects?</h4>' +
|
|
10618
10644
|
'<p>Yes. Each project has its own <code>.neohive/</code> directory. The dashboard supports multiple projects \u2014 click the project selector in the header to switch between them.</p>' +
|
|
10619
10645
|
'<h4>How do I send a message to agents from the dashboard?</h4>' +
|
|
10620
|
-
'<p>Click any agent\'s avatar in the Messages tab. A dialog lets you type and send a message that agents will receive on their next <code>
|
|
10646
|
+
'<p>Click any agent\'s avatar in the Messages tab. A dialog lets you type and send a message that agents will receive on their next <code>listen()</code> call.</p>' +
|
|
10621
10647
|
'</div>' +
|
|
10622
10648
|
|
|
10623
10649
|
'</div>';
|
|
@@ -10636,8 +10662,8 @@ function copyLaunchPrompt() {
|
|
|
10636
10662
|
if (!prompt) {
|
|
10637
10663
|
var agentName = document.getElementById('launch-name').value.trim();
|
|
10638
10664
|
prompt = agentName
|
|
10639
|
-
? 'You are agent "' + agentName + '". Use the register tool to register as "' + agentName + '", then use
|
|
10640
|
-
: 'Register with the neohive MCP tools and use
|
|
10665
|
+
? 'You are agent "' + agentName + '". Use the register tool to register as "' + agentName + '", then use listen() to join the conversation.'
|
|
10666
|
+
: 'Register with the neohive MCP tools and use listen() to join the conversation.';
|
|
10641
10667
|
}
|
|
10642
10668
|
navigator.clipboard.writeText(prompt).then(function() {
|
|
10643
10669
|
var resultEl = document.getElementById('launch-result');
|
|
@@ -10651,7 +10677,11 @@ function copyLaunchPrompt() {
|
|
|
10651
10677
|
|
|
10652
10678
|
function toggleSettingsMenu() {
|
|
10653
10679
|
var menu = document.getElementById('settings-menu');
|
|
10654
|
-
if (menu)
|
|
10680
|
+
if (menu) {
|
|
10681
|
+
var opening = menu.style.display === 'none';
|
|
10682
|
+
menu.style.display = opening ? 'block' : 'none';
|
|
10683
|
+
if (opening) { initIdlePollInput(); initListenPollInput(); }
|
|
10684
|
+
}
|
|
10655
10685
|
}
|
|
10656
10686
|
// Close settings when clicking outside
|
|
10657
10687
|
document.addEventListener('click', function(e) {
|
|
@@ -10947,6 +10977,8 @@ function setCoordinatorMode(mode) {
|
|
|
10947
10977
|
showToast('!', 'Failed: ' + data.error);
|
|
10948
10978
|
} else {
|
|
10949
10979
|
showToast('✓', 'Coordinator mode: ' + (mode === 'responsive' ? 'Stay with me' : 'Run autonomously'));
|
|
10980
|
+
var listenInput = document.getElementById('settings-listen-poll-input');
|
|
10981
|
+
if (listenInput) listenInput.value = mode === 'responsive' ? 120 : 90;
|
|
10950
10982
|
renderOverview();
|
|
10951
10983
|
}
|
|
10952
10984
|
}).catch(function() {
|
|
@@ -10954,6 +10986,60 @@ function setCoordinatorMode(mode) {
|
|
|
10954
10986
|
});
|
|
10955
10987
|
}
|
|
10956
10988
|
|
|
10989
|
+
// ==================== IDLE POLL INTERVAL ====================
|
|
10990
|
+
|
|
10991
|
+
function setIdlePollInterval(value) {
|
|
10992
|
+
var secs = parseInt(value, 10);
|
|
10993
|
+
if (isNaN(secs) || secs < 10) return;
|
|
10994
|
+
lttFetch('/api/config', {
|
|
10995
|
+
method: 'POST',
|
|
10996
|
+
headers: { 'Content-Type': 'application/json' },
|
|
10997
|
+
body: JSON.stringify({ idle_poll_interval: secs })
|
|
10998
|
+
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
10999
|
+
if (data.error) {
|
|
11000
|
+
showToast('!', 'Failed: ' + data.error);
|
|
11001
|
+
} else {
|
|
11002
|
+
showToast('✓', 'Idle poll interval: ' + secs + 's');
|
|
11003
|
+
}
|
|
11004
|
+
}).catch(function() {
|
|
11005
|
+
showToast('!', 'Failed to save idle poll interval');
|
|
11006
|
+
});
|
|
11007
|
+
}
|
|
11008
|
+
|
|
11009
|
+
function initIdlePollInput() {
|
|
11010
|
+
lttFetch('/api/config').then(function(r) { return r.json(); }).then(function(cfg) {
|
|
11011
|
+
var input = document.getElementById('settings-idle-poll-input');
|
|
11012
|
+
if (input && cfg.idle_poll_interval) input.value = cfg.idle_poll_interval;
|
|
11013
|
+
}).catch(function() {});
|
|
11014
|
+
}
|
|
11015
|
+
|
|
11016
|
+
// ==================== LISTEN POLL INTERVAL ====================
|
|
11017
|
+
|
|
11018
|
+
function setListenPollInterval(value) {
|
|
11019
|
+
var secs = parseInt(value, 10);
|
|
11020
|
+
if (isNaN(secs) || secs < 30) return;
|
|
11021
|
+
lttFetch('/api/config', {
|
|
11022
|
+
method: 'POST',
|
|
11023
|
+
headers: { 'Content-Type': 'application/json' },
|
|
11024
|
+
body: JSON.stringify({ listen_poll_interval: secs })
|
|
11025
|
+
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
11026
|
+
if (data.error) {
|
|
11027
|
+
showToast('!', 'Failed: ' + data.error);
|
|
11028
|
+
} else {
|
|
11029
|
+
showToast('✓', 'Listen timeout: ' + secs + 's');
|
|
11030
|
+
}
|
|
11031
|
+
}).catch(function() {
|
|
11032
|
+
showToast('!', 'Failed to save listen timeout');
|
|
11033
|
+
});
|
|
11034
|
+
}
|
|
11035
|
+
|
|
11036
|
+
function initListenPollInput() {
|
|
11037
|
+
lttFetch('/api/config').then(function(r) { return r.json(); }).then(function(cfg) {
|
|
11038
|
+
var input = document.getElementById('settings-listen-poll-input');
|
|
11039
|
+
if (input && cfg.listen_poll_interval) input.value = cfg.listen_poll_interval;
|
|
11040
|
+
}).catch(function() {});
|
|
11041
|
+
}
|
|
11042
|
+
|
|
10957
11043
|
// ==================== TOAST NOTIFICATIONS ====================
|
|
10958
11044
|
|
|
10959
11045
|
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`
|
|
@@ -2656,9 +2662,9 @@ const server = http.createServer(async (req, res) => {
|
|
|
2656
2662
|
prompt += `**Instructions:**\n`;
|
|
2657
2663
|
prompt += `1. Register as "${agentName}" using the register tool\n`;
|
|
2658
2664
|
prompt += `2. Call get_briefing() for full project context\n`;
|
|
2659
|
-
prompt += `3. Call
|
|
2665
|
+
prompt += `3. Call listen() to rejoin the conversation\n`;
|
|
2660
2666
|
prompt += `4. Announce you're back and pick up your active tasks\n`;
|
|
2661
|
-
prompt += `5. Stay in
|
|
2667
|
+
prompt += `5. Stay in listen() loop — never stop listening\n`;
|
|
2662
2668
|
|
|
2663
2669
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2664
2670
|
res.end(JSON.stringify({
|
|
@@ -2711,7 +2717,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
2711
2717
|
try {
|
|
2712
2718
|
const messagesFile = filePath('messages.jsonl', projectPath);
|
|
2713
2719
|
const historyFile = filePath('history.jsonl', projectPath);
|
|
2714
|
-
const modeText = newMode === 'responsive' ? 'Coordinator stays with human, uses
|
|
2720
|
+
const modeText = newMode === 'responsive' ? 'Coordinator stays with human, uses messages(action="check").' : 'Coordinator runs autonomously in listen() loop.';
|
|
2715
2721
|
const sysMsg = { id: Date.now().toString(36) + Math.random().toString(36).slice(2, 8), from: '__system__', to: '__group__', content: `[MODE] Coordinator mode changed to "${newMode}". ${modeText} Coordinator: call get_guide() to update your instructions.`, timestamp: new Date().toISOString(), system: true };
|
|
2716
2722
|
fs.appendFileSync(messagesFile, JSON.stringify(sysMsg) + '\n');
|
|
2717
2723
|
fs.appendFileSync(historyFile, JSON.stringify(sysMsg) + '\n');
|
|
@@ -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) {
|