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/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 aliveAgentNames = Object.keys(agents).filter(n => isPidAlive(agents[n].pid, agents[n].last_activity));
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
- return !aliveAgentNames.every(n => n === m.from || (perAgentConsumed[n] && perAgentConsumed[n].has(m.id)));
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
- // Data dir lives in the project where the CLI runs, not where the package is installed
7
- const DATA_DIR = process.env.NEOHIVE_DATA_DIR || path.join(process.cwd(), '.neohive');
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
- const wait = Date.now(); while (Date.now() - wait < backoff) {}
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
- const wait = Date.now(); while (Date.now() - wait < 50) {}
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
- const wait = Date.now(); while (Date.now() - wait < backoff) {}
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.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": "./cli.js"
7
+ "neohive": "cli.js"
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node server.js",