neohive 6.0.0 → 6.0.3
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 +7 -0
- package/cli.js +134 -33
- package/conversation-templates/autonomous-feature.json +54 -4
- package/conversation-templates/code-review.json +41 -3
- package/conversation-templates/debug-squad.json +41 -3
- package/conversation-templates/feature-build.json +41 -3
- package/conversation-templates/research-write.json +41 -3
- package/dashboard.html +2001 -691
- package/dashboard.js +719 -67
- package/lib/compact.js +5 -2
- package/lib/config.js +4 -3
- package/lib/file-io.js +3 -3
- package/lib/resolve-server-data-dir.js +96 -0
- package/package.json +2 -2
- package/server.js +872 -148
- package/templates/debate.json +24 -5
- package/templates/managed.json +48 -9
- package/templates/pair.json +22 -3
- package/templates/review.json +26 -5
- package/templates/team.json +38 -8
package/lib/compact.js
CHANGED
|
@@ -62,7 +62,8 @@ function autoCompact() {
|
|
|
62
62
|
const messages = lines.map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
|
|
63
63
|
|
|
64
64
|
const agents = getAgents();
|
|
65
|
-
const
|
|
65
|
+
const allAgentNames = Object.keys(agents);
|
|
66
|
+
const retentionMs = (parseInt(process.env.NEOHIVE_RETENTION_HOURS) || 24) * 3600000;
|
|
66
67
|
const allConsumed = new Set();
|
|
67
68
|
const perAgentConsumed = {};
|
|
68
69
|
if (fs.existsSync(DATA_DIR)) {
|
|
@@ -80,7 +81,9 @@ function autoCompact() {
|
|
|
80
81
|
|
|
81
82
|
const active = messages.filter(m => {
|
|
82
83
|
if (m.to === '__group__') {
|
|
83
|
-
|
|
84
|
+
const msgTime = new Date(m.timestamp).getTime();
|
|
85
|
+
if (msgTime < Date.now() - retentionMs) return false;
|
|
86
|
+
return !allAgentNames.every(n => n === m.from || (perAgentConsumed[n] && perAgentConsumed[n].has(m.id)));
|
|
84
87
|
}
|
|
85
88
|
if (!allConsumed.has(m.id)) return true;
|
|
86
89
|
return false;
|
package/lib/config.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const { resolveDataDirForServer } = require('./resolve-server-data-dir');
|
|
5
6
|
|
|
6
|
-
//
|
|
7
|
-
const DATA_DIR =
|
|
7
|
+
// Same rules as server.js: env, else repo .cursor/mcp.json sibling, else cwd/.neohive
|
|
8
|
+
const DATA_DIR = resolveDataDirForServer(path.join(__dirname, '..'));
|
|
8
9
|
|
|
9
10
|
// File paths for all shared data
|
|
10
11
|
const MESSAGES_FILE = path.join(DATA_DIR, 'messages.jsonl');
|
|
@@ -35,7 +36,7 @@ const CHANNELS_FILE_PATH = path.join(DATA_DIR, 'channels.json');
|
|
|
35
36
|
// Constants
|
|
36
37
|
const MAX_CONTENT_BYTES = 1000000; // 1 MB max message size
|
|
37
38
|
const CURRENT_DATA_VERSION = 1;
|
|
38
|
-
const RESERVED_NAMES = ['__system__', '__all__', '__open__', '__close__', 'system', 'dashboard', 'Dashboard'];
|
|
39
|
+
const RESERVED_NAMES = ['__system__', '__all__', '__open__', '__close__', '__user__', 'system', 'dashboard', 'Dashboard'];
|
|
39
40
|
|
|
40
41
|
// Config helpers
|
|
41
42
|
function getConfig() {
|
package/lib/file-io.js
CHANGED
|
@@ -107,7 +107,7 @@ function lockAgentsFile() {
|
|
|
107
107
|
while (Date.now() - start < maxWait) {
|
|
108
108
|
try { fs.writeFileSync(AGENTS_LOCK, String(process.pid), { flag: 'wx' }); return true; }
|
|
109
109
|
catch {}
|
|
110
|
-
|
|
110
|
+
try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, backoff); } catch {}
|
|
111
111
|
backoff = Math.min(backoff * 2, 500);
|
|
112
112
|
}
|
|
113
113
|
try { fs.unlinkSync(AGENTS_LOCK); } catch {}
|
|
@@ -123,7 +123,7 @@ function lockConfigFile() {
|
|
|
123
123
|
while (Date.now() - start < maxWait) {
|
|
124
124
|
try { fs.writeFileSync(CONFIG_LOCK, String(process.pid), { flag: 'wx' }); return true; }
|
|
125
125
|
catch {}
|
|
126
|
-
|
|
126
|
+
try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50); } catch {}
|
|
127
127
|
}
|
|
128
128
|
try { fs.unlinkSync(CONFIG_LOCK); } catch {}
|
|
129
129
|
try { fs.writeFileSync(CONFIG_LOCK, String(process.pid), { flag: 'wx' }); return true; } catch {}
|
|
@@ -139,7 +139,7 @@ function withFileLock(filePath, fn) {
|
|
|
139
139
|
while (Date.now() - start < maxWait) {
|
|
140
140
|
try { fs.writeFileSync(lockPath, String(process.pid), { flag: 'wx' }); break; }
|
|
141
141
|
catch {}
|
|
142
|
-
|
|
142
|
+
try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, backoff); } catch {}
|
|
143
143
|
backoff = Math.min(backoff * 2, 500);
|
|
144
144
|
if (Date.now() - start >= maxWait) {
|
|
145
145
|
try {
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
function normalizeNeohiveDataDirString(raw, workspaceRoot) {
|
|
8
|
+
if (raw == null || typeof raw !== 'string') return null;
|
|
9
|
+
let d = raw.trim();
|
|
10
|
+
if (!d) return null;
|
|
11
|
+
d = d.replace(/\$\{workspaceFolder\}/gi, workspaceRoot);
|
|
12
|
+
return path.isAbsolute(d) ? path.resolve(d) : path.resolve(workspaceRoot, d);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Same candidate files as dashboard.js readNeohiveDataDirFromMcpConfigs */
|
|
16
|
+
function readNeohiveDataDirFromMcpConfigs(projectRoot) {
|
|
17
|
+
const candidates = [
|
|
18
|
+
path.join(projectRoot, '.cursor', 'mcp.json'),
|
|
19
|
+
path.join(projectRoot, '.mcp.json'),
|
|
20
|
+
path.join(projectRoot, '.gemini', 'settings.json'),
|
|
21
|
+
];
|
|
22
|
+
for (const filePath of candidates) {
|
|
23
|
+
if (!fs.existsSync(filePath)) continue;
|
|
24
|
+
try {
|
|
25
|
+
const j = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
26
|
+
const nh = j.mcpServers && j.mcpServers.neohive;
|
|
27
|
+
const raw = nh && nh.env && nh.env.NEOHIVE_DATA_DIR;
|
|
28
|
+
const out = normalizeNeohiveDataDirString(raw, projectRoot);
|
|
29
|
+
if (out) return out;
|
|
30
|
+
} catch {
|
|
31
|
+
/* ignore */
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function findDataDirByWalkingUpFrom(startDir) {
|
|
38
|
+
let dir = path.resolve(startDir);
|
|
39
|
+
const root = path.parse(dir).root;
|
|
40
|
+
for (let depth = 0; depth < 32 && dir !== root; depth++) {
|
|
41
|
+
const fromMcp = readNeohiveDataDirFromMcpConfigs(dir);
|
|
42
|
+
if (fromMcp) return fromMcp;
|
|
43
|
+
dir = path.dirname(dir);
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** User-level Cursor MCP may define neohive with an absolute data dir */
|
|
49
|
+
function readNeohiveDirFromUserCursorMcp() {
|
|
50
|
+
const userMcp = path.join(os.homedir(), '.cursor', 'mcp.json');
|
|
51
|
+
if (!fs.existsSync(userMcp)) return null;
|
|
52
|
+
try {
|
|
53
|
+
const j = JSON.parse(fs.readFileSync(userMcp, 'utf8'));
|
|
54
|
+
const nh = j.mcpServers && j.mcpServers.neohive;
|
|
55
|
+
const raw = nh && nh.env && nh.env.NEOHIVE_DATA_DIR;
|
|
56
|
+
if (raw == null || typeof raw !== 'string') return null;
|
|
57
|
+
const d = raw.trim();
|
|
58
|
+
if (!d || /\$\{workspaceFolder\}/i.test(d)) return null;
|
|
59
|
+
return path.resolve(d);
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Neohive data directory for the MCP / CLI process.
|
|
67
|
+
* Cursor often spawns MCP with cwd=user home and omits NEOHIVE_DATA_DIR in the child env.
|
|
68
|
+
* We mirror dashboard resolution: walk ancestors of cwd for MCP configs, then package sibling,
|
|
69
|
+
* then ~/.cursor/mcp.json with an absolute path, then cwd/.neohive.
|
|
70
|
+
*
|
|
71
|
+
* @param {string} serverJsDir - __dirname of server.js (the agent-bridge folder)
|
|
72
|
+
*/
|
|
73
|
+
function resolveDataDirForServer(serverJsDir) {
|
|
74
|
+
const raw = process.env.NEOHIVE_DATA_DIR || process.env.NEOHIVE_DATA;
|
|
75
|
+
if (raw != null && String(raw).trim() !== '') {
|
|
76
|
+
return path.resolve(String(raw).trim());
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const fromWalk = findDataDirByWalkingUpFrom(process.cwd());
|
|
80
|
+
if (fromWalk) return fromWalk;
|
|
81
|
+
|
|
82
|
+
const parent = path.join(serverJsDir, '..');
|
|
83
|
+
if (fs.existsSync(path.join(parent, '.cursor', 'mcp.json'))) {
|
|
84
|
+
return path.join(parent, '.neohive');
|
|
85
|
+
}
|
|
86
|
+
if (fs.existsSync(path.join(serverJsDir, '.cursor', 'mcp.json'))) {
|
|
87
|
+
return path.join(serverJsDir, '.neohive');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const fromUser = readNeohiveDirFromUserCursorMcp();
|
|
91
|
+
if (fromUser) return fromUser;
|
|
92
|
+
|
|
93
|
+
return path.join(process.cwd(), '.neohive');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = { resolveDataDirForServer };
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neohive",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.3",
|
|
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": {
|
|
7
|
-
"neohive": "
|
|
7
|
+
"neohive": "cli.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node server.js",
|