neohive 6.1.0 → 6.1.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/cli.js +18 -1
- package/dashboard.js +26 -9
- package/lib/agents.js +9 -3
- package/logo.png +0 -0
- package/package.json +1 -1
- package/server.js +12 -18
package/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ const path = require('path');
|
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const { execSync } = require('child_process');
|
|
7
7
|
const { upsertNeohiveMcpInToml } = require('./lib/codex-neohive-toml');
|
|
8
|
+
const pkg = require('./package.json');
|
|
8
9
|
|
|
9
10
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
10
11
|
// CLI_CONFIG — centralized constants for the Neohive CLI
|
|
@@ -21,7 +22,7 @@ const command = process.argv[2];
|
|
|
21
22
|
|
|
22
23
|
function printUsage() {
|
|
23
24
|
console.log(`
|
|
24
|
-
Neohive
|
|
25
|
+
Neohive v${pkg.version}
|
|
25
26
|
The MCP collaboration layer for AI CLI tools.
|
|
26
27
|
|
|
27
28
|
Usage:
|
|
@@ -717,6 +718,22 @@ function reset() {
|
|
|
717
718
|
console.log(' [warn] Could not archive: ' + e.message + ' — proceeding with reset anyway.');
|
|
718
719
|
}
|
|
719
720
|
|
|
721
|
+
// Kill any running MCP server processes before wiping data.
|
|
722
|
+
// Otherwise orphaned heartbeat intervals keep writing into the fresh directory.
|
|
723
|
+
try {
|
|
724
|
+
const agentsFile = path.join(targetDir, 'agents.json');
|
|
725
|
+
if (fs.existsSync(agentsFile)) {
|
|
726
|
+
const agents = JSON.parse(fs.readFileSync(agentsFile, 'utf8'));
|
|
727
|
+
let killed = 0;
|
|
728
|
+
for (const [name, info] of Object.entries(agents)) {
|
|
729
|
+
if (info.pid) {
|
|
730
|
+
try { process.kill(info.pid, 'SIGTERM'); killed++; } catch {}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
if (killed > 0) console.log(' [ok] Terminated ' + killed + ' running agent process(es)');
|
|
734
|
+
}
|
|
735
|
+
} catch {}
|
|
736
|
+
|
|
720
737
|
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
721
738
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
722
739
|
console.log(' Cleared all data from ' + targetDir);
|
package/dashboard.js
CHANGED
|
@@ -5,6 +5,7 @@ const path = require('path');
|
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const { spawn } = require('child_process');
|
|
7
7
|
const { upsertNeohiveMcpInToml } = require('./lib/codex-neohive-toml');
|
|
8
|
+
const pkg = require(path.join(__dirname, 'package.json'));
|
|
8
9
|
const { readIdeActivity, applyIdeActivityHint } = require('./lib/ide-activity');
|
|
9
10
|
const _audit = require('./lib/audit');
|
|
10
11
|
|
|
@@ -312,22 +313,23 @@ function readJson(file) {
|
|
|
312
313
|
}
|
|
313
314
|
|
|
314
315
|
function isPidAlive(pid, lastActivity) {
|
|
315
|
-
const STALE_THRESHOLD = 30000; // 30s — 3x heartbeat interval
|
|
316
|
+
const STALE_THRESHOLD = 30000; // 30s — 3x heartbeat interval
|
|
317
|
+
const PID_TRUST_WINDOW = 60000; // 60s — beyond this, PID check is unreliable (OS reuses PIDs)
|
|
316
318
|
|
|
317
|
-
// PRIORITY 1: Trust heartbeat freshness over PID status
|
|
318
|
-
// Heartbeats are written by the actual running process — if fresh, agent is alive
|
|
319
|
-
// regardless of whether process.kill can see the PID
|
|
320
319
|
if (lastActivity) {
|
|
321
320
|
const stale = Date.now() - new Date(lastActivity).getTime();
|
|
322
321
|
if (stale < STALE_THRESHOLD) return true;
|
|
322
|
+
// A real neohive agent writes heartbeat every 10s. If 60s have passed
|
|
323
|
+
// without one, the PID belongs to a different process (OS recycled it).
|
|
324
|
+
if (stale > PID_TRUST_WINDOW) return false;
|
|
323
325
|
}
|
|
324
326
|
|
|
325
|
-
//
|
|
327
|
+
// Heartbeat is stale but within the trust window — verify PID as fallback
|
|
326
328
|
try {
|
|
327
329
|
process.kill(pid, 0);
|
|
328
|
-
return true;
|
|
330
|
+
return true;
|
|
329
331
|
} catch {
|
|
330
|
-
return false;
|
|
332
|
+
return false;
|
|
331
333
|
}
|
|
332
334
|
}
|
|
333
335
|
|
|
@@ -3958,12 +3960,27 @@ function startFileWatcher() {
|
|
|
3958
3960
|
sseNotifyAll(changeType);
|
|
3959
3961
|
}, 2000);
|
|
3960
3962
|
});
|
|
3961
|
-
fsWatcher.on('error', () => {
|
|
3963
|
+
fsWatcher.on('error', () => {
|
|
3964
|
+
setTimeout(startFileWatcher, 1000);
|
|
3965
|
+
});
|
|
3962
3966
|
} catch {}
|
|
3963
3967
|
}
|
|
3964
3968
|
|
|
3965
3969
|
startFileWatcher();
|
|
3966
3970
|
|
|
3971
|
+
// macOS fs.watch() silently stops emitting events when the watched directory is
|
|
3972
|
+
// deleted and recreated (e.g. reset --force). The watcher object stays non-null
|
|
3973
|
+
// but is dead. Force-restart it every 30s to guarantee the dashboard stays live.
|
|
3974
|
+
let _lastWatcherRestart = Date.now();
|
|
3975
|
+
setInterval(() => {
|
|
3976
|
+
const dataDir = resolveDataDir();
|
|
3977
|
+
if (!fs.existsSync(dataDir)) return;
|
|
3978
|
+
if (!fsWatcher || Date.now() - _lastWatcherRestart > 30000) {
|
|
3979
|
+
startFileWatcher();
|
|
3980
|
+
_lastWatcherRestart = Date.now();
|
|
3981
|
+
}
|
|
3982
|
+
}, 5000).unref();
|
|
3983
|
+
|
|
3967
3984
|
server.on('error', (err) => {
|
|
3968
3985
|
if (err.code === 'EADDRINUSE') {
|
|
3969
3986
|
console.error(`\n Error: Port ${PORT} is already in use.`);
|
|
@@ -3979,7 +3996,7 @@ server.listen(PORT, LAN_MODE ? '0.0.0.0' : '127.0.0.1', () => {
|
|
|
3979
3996
|
const dataDir = resolveDataDir();
|
|
3980
3997
|
const lanIP = getLanIP();
|
|
3981
3998
|
console.log('');
|
|
3982
|
-
console.log(
|
|
3999
|
+
console.log(` Neohive Dashboard v${pkg.version}`);
|
|
3983
4000
|
console.log(' ============================================');
|
|
3984
4001
|
console.log(' Dashboard: http://localhost:' + PORT);
|
|
3985
4002
|
if (LAN_MODE && lanIP) {
|
package/lib/agents.js
CHANGED
|
@@ -12,6 +12,7 @@ const _pidAliveCache = {};
|
|
|
12
12
|
let _isAutonomousMode = () => false;
|
|
13
13
|
function setAutonomousModeCheck(fn) { _isAutonomousMode = fn; }
|
|
14
14
|
|
|
15
|
+
const PID_TRUST_WINDOW_MS = 60000;
|
|
15
16
|
function isPidAlive(pid, lastActivity) {
|
|
16
17
|
const cacheKey = `${pid}_${lastActivity}`;
|
|
17
18
|
const cached = _pidAliveCache[cacheKey];
|
|
@@ -22,9 +23,14 @@ function isPidAlive(pid, lastActivity) {
|
|
|
22
23
|
|
|
23
24
|
if (lastActivity) {
|
|
24
25
|
const stale = Date.now() - new Date(lastActivity).getTime();
|
|
25
|
-
if (stale < STALE_THRESHOLD)
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
if (stale < STALE_THRESHOLD) {
|
|
27
|
+
alive = true;
|
|
28
|
+
} else if (stale > PID_TRUST_WINDOW_MS) {
|
|
29
|
+
alive = false;
|
|
30
|
+
} else {
|
|
31
|
+
try { process.kill(pid, 0); alive = true; } catch { alive = false; }
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
28
34
|
try { process.kill(pid, 0); alive = true; } catch { alive = false; }
|
|
29
35
|
}
|
|
30
36
|
_pidAliveCache[cacheKey] = { alive, ts: Date.now() };
|
package/logo.png
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -6,6 +6,7 @@ const {
|
|
|
6
6
|
} = require('@modelcontextprotocol/sdk/types.js');
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
|
+
const pkg = require('./package.json');
|
|
9
10
|
|
|
10
11
|
// --- Modular infrastructure (lib/) ---
|
|
11
12
|
// These modules are the canonical implementations. The inline code below
|
|
@@ -453,40 +454,33 @@ function getAcks() {
|
|
|
453
454
|
}
|
|
454
455
|
}
|
|
455
456
|
|
|
456
|
-
// Cache for isPidAlive results — avoids redundant process.kill calls at 100-agent scale
|
|
457
457
|
const _pidAliveCache = {};
|
|
458
|
+
const PID_TRUST_WINDOW_MS = 60000; // Beyond 60s without heartbeat, PID is unreliable (OS reuses PIDs)
|
|
458
459
|
function isPidAlive(pid, lastActivity) {
|
|
459
|
-
// Cache with 5s TTL — PID status doesn't change faster than heartbeats
|
|
460
460
|
const cacheKey = `${pid}_${lastActivity}`;
|
|
461
461
|
const cached = _pidAliveCache[cacheKey];
|
|
462
462
|
if (cached && Date.now() - cached.ts < SERVER_CONFIG.AGENT_CACHE_TTL_MS) return cached.alive;
|
|
463
463
|
|
|
464
|
-
// 30s stale threshold — 3x the 10s heartbeat interval, catches dead agents faster
|
|
465
464
|
const STALE_THRESHOLD = SERVER_CONFIG.AGENT_STALE_THRESHOLD_MS;
|
|
466
465
|
let alive = false;
|
|
467
466
|
|
|
468
|
-
// PRIORITY 1: Trust heartbeat freshness over PID status
|
|
469
|
-
// Heartbeat files are written by the actual running process — if fresh, agent is alive
|
|
470
|
-
// regardless of whether process.kill can see the PID (cross-process PID visibility issues)
|
|
471
467
|
if (lastActivity) {
|
|
472
468
|
const stale = Date.now() - new Date(lastActivity).getTime();
|
|
473
469
|
if (stale < STALE_THRESHOLD) {
|
|
474
470
|
alive = true;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
// PRIORITY 2: If heartbeat is stale, verify PID is actually dead
|
|
479
|
-
if (!alive) {
|
|
480
|
-
try {
|
|
481
|
-
process.kill(pid, 0);
|
|
482
|
-
alive = true; // PID exists — agent is alive even with stale heartbeat
|
|
483
|
-
} catch {
|
|
484
|
-
// PID dead AND heartbeat stale — agent is truly dead
|
|
471
|
+
} else if (stale > PID_TRUST_WINDOW_MS) {
|
|
472
|
+
// A real neohive agent writes heartbeat every 10s. If 60s have passed
|
|
473
|
+
// without one, the PID belongs to a different process (OS recycled it).
|
|
485
474
|
alive = false;
|
|
475
|
+
} else {
|
|
476
|
+
// Within trust window — verify PID as fallback
|
|
477
|
+
try { process.kill(pid, 0); alive = true; } catch { alive = false; }
|
|
486
478
|
}
|
|
479
|
+
} else {
|
|
480
|
+
try { process.kill(pid, 0); alive = true; } catch { alive = false; }
|
|
487
481
|
}
|
|
482
|
+
|
|
488
483
|
_pidAliveCache[cacheKey] = { alive, ts: Date.now() };
|
|
489
|
-
// Evict old entries (keep cache small)
|
|
490
484
|
const keys = Object.keys(_pidAliveCache);
|
|
491
485
|
if (keys.length > 200) {
|
|
492
486
|
const cutoff = Date.now() - SERVER_CONFIG.POLL_INTERVAL_MS * 5;
|
|
@@ -7242,7 +7236,7 @@ const messaging = require('./tools/messaging')(_messagingCtx);
|
|
|
7242
7236
|
// --- MCP Server setup ---
|
|
7243
7237
|
|
|
7244
7238
|
const server = new Server(
|
|
7245
|
-
{ name: 'neohive', version:
|
|
7239
|
+
{ name: 'neohive', version: pkg.version },
|
|
7246
7240
|
{ capabilities: { tools: { listChanged: true } } }
|
|
7247
7241
|
);
|
|
7248
7242
|
|