@datafrog-io/n2n-nexus 0.3.0 → 0.3.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/README.md CHANGED
@@ -102,36 +102,22 @@
102
102
 
103
103
  Add to your MCP config file (e.g., `claude_desktop_config.json` or Cursor MCP settings):
104
104
 
105
- #### Leader AI
106
105
  ```json
107
106
  {
108
107
  "mcpServers": {
109
108
  "n2n-nexus": {
110
109
  "command": "npx",
111
- "args": [
112
- "-y",
113
- "@datafrog-io/n2n-nexus",
114
- "--root", "D:/DevSpace/Nexus_Storage"
115
- ]
110
+ "args": ["-y", "@datafrog-io/n2n-nexus"]
116
111
  }
117
112
  }
118
113
  }
119
114
  ```
120
115
 
121
- #### Collaborator AI
116
+ > **Zero-Config**: No `--id` or `--host` needed. Just run and collaborate!
117
+
118
+ **Optional**: Use `--root` to specify a custom storage path:
122
119
  ```json
123
- {
124
- "mcpServers": {
125
- "n2n-nexus": {
126
- "command": "npx",
127
- "args": [
128
- "-y",
129
- "@datafrog-io/n2n-nexus",
130
- "--root", "D:/DevSpace/Nexus_Storage"
131
- ]
132
- }
133
- }
134
- }
120
+ "args": ["-y", "@datafrog-io/n2n-nexus", "--root", "/path/to/storage"]
135
121
  ```
136
122
 
137
123
  ### CLI Arguments
@@ -174,9 +160,11 @@ The following files demonstrate a real orchestration session where **4 AI agents
174
160
 
175
161
  ## ⭐ Support This Project
176
162
 
177
- If **n2ns Nexus** helps you build better AI workflows, consider giving it a star! Your support helps us improve and motivates continued development.
163
+ If **n2ns Nexus** helps you build better AI workflows, consider giving it a star!
178
164
 
179
- [![Star on GitHub](https://img.shields.io/github/stars/n2ns/n2n-nexus?style=social)](https://github.com/n2ns/n2n-nexus)
165
+ <a href="https://github.com/n2ns/n2n-nexus">
166
+ <img src="https://img.shields.io/github/stars/n2ns/n2n-nexus?style=for-the-badge&logo=github&logoColor=white&label=Star%20on%20GitHub" alt="Star on GitHub">
167
+ </a>
180
168
 
181
169
  ---
182
170
 
package/build/config.js CHANGED
@@ -106,56 +106,72 @@ async function probeHost(port) {
106
106
  */
107
107
  async function isHostAutoElection(root) {
108
108
  const startPort = 5688;
109
- const endPort = 5700;
110
- // Phase 1: Probe-First - Check if any Host already exists
111
- for (let port = startPort; port <= endPort; port++) {
112
- const probe = await probeHost(port);
113
- if (probe.isNexus) {
114
- return { isHost: false, port, rootStorage: probe.rootStorage };
109
+ const endPort = 5800;
110
+ let retryCount = 0;
111
+ // eslint-disable-next-line no-constant-condition
112
+ while (true) {
113
+ // Phase 1: Probe-First - Check if any Host already exists (Concurrent Batch Scan)
114
+ const BATCH_SIZE = 20;
115
+ for (let batchStart = startPort; batchStart <= endPort; batchStart += BATCH_SIZE) {
116
+ const batchEnd = Math.min(batchStart + BATCH_SIZE - 1, endPort);
117
+ const promises = [];
118
+ for (let port = batchStart; port <= batchEnd; port++) {
119
+ promises.push(probeHost(port).then(res => ({ port, ...res })));
120
+ }
121
+ const results = await Promise.all(promises);
122
+ const found = results.find(r => r.isNexus);
123
+ if (found) {
124
+ return { isHost: false, port: found.port, rootStorage: found.rootStorage };
125
+ }
115
126
  }
116
- }
117
- // Phase 2: No Host found, attempt to become Host
118
- for (let port = startPort; port <= endPort; port++) {
119
- const result = await new Promise((resolve) => {
120
- const server = http.createServer((req, res) => {
121
- if (req.url === "/hello") {
122
- res.writeHead(200, { "Content-Type": "application/json" });
123
- res.end(JSON.stringify({
124
- service: "n2n-nexus",
125
- role: "host",
126
- version: pkg.version,
127
- rootStorage: root
128
- }));
129
- return;
130
- }
131
- res.writeHead(404);
132
- res.end();
133
- });
134
- server.on("error", (err) => {
135
- if (err.code === "EADDRINUSE") {
136
- resolve({ isHost: false });
137
- }
138
- else {
139
- resolve({ isHost: false });
140
- }
141
- });
142
- server.listen(port, "127.0.0.1", () => {
143
- resolve({ isHost: true, server });
127
+ // Phase 2: No Host found, attempt to become Host
128
+ for (let port = startPort; port <= endPort; port++) {
129
+ const result = await new Promise((resolve) => {
130
+ const server = http.createServer((req, res) => {
131
+ if (req.url === "/hello") {
132
+ res.writeHead(200, { "Content-Type": "application/json" });
133
+ res.end(JSON.stringify({
134
+ service: "n2n-nexus",
135
+ role: "host",
136
+ version: pkg.version,
137
+ rootStorage: root
138
+ }));
139
+ return;
140
+ }
141
+ res.writeHead(404);
142
+ res.end();
143
+ });
144
+ server.on("error", (err) => {
145
+ if (err.code === "EADDRINUSE") {
146
+ resolve({ isHost: false });
147
+ }
148
+ else {
149
+ resolve({ isHost: false });
150
+ }
151
+ });
152
+ server.listen(port, "127.0.0.1", () => {
153
+ resolve({ isHost: true, server });
154
+ });
144
155
  });
145
- });
146
- if (result.isHost) {
147
- return { isHost: true, port, server: result.server };
148
- }
149
- // Phase 3: Bind failed - another Guest won. Wait then join winner.
150
- await new Promise(r => setTimeout(r, 10000)); // Give winner 10s to start /hello
151
- const probe = await probeHost(port);
152
- if (probe.isNexus) {
153
- return { isHost: false, port, rootStorage: probe.rootStorage };
156
+ if (result.isHost) {
157
+ return { isHost: true, port, server: result.server };
158
+ }
159
+ // Phase 3: Bind failed - another Guest won. Wait then join winner.
160
+ await new Promise(r => setTimeout(r, 10000)); // Give winner 10s to start /hello
161
+ const probe = await probeHost(port);
162
+ if (probe.isNexus) {
163
+ return { isHost: false, port, rootStorage: probe.rootStorage };
164
+ }
165
+ // If still not Nexus, try next port (occupied by non-Nexus service)
154
166
  }
155
- // If still not Nexus, try next port (occupied by non-Nexus service)
167
+ // Fallback: All ports occupied - progressive backoff retry
168
+ // First 5 attempts: 1 minute interval, then 2 minute interval
169
+ const waitTime = retryCount < 5 ? 60000 : 120000;
170
+ const intervalStr = retryCount < 5 ? "1 minute" : "2 minutes";
171
+ console.error(`[Nexus] All ports ${startPort}-${endPort} occupied. Retry #${retryCount + 1} in ${intervalStr}...`);
172
+ await new Promise(r => setTimeout(r, waitTime));
173
+ retryCount++;
156
174
  }
157
- // Fallback: become Host on startPort (should rarely happen)
158
- return { isHost: true, port: startPort };
159
175
  }
160
176
  /**
161
177
  * Automatic Project Name Detection
package/build/index.js CHANGED
@@ -196,21 +196,28 @@ class NexusServer {
196
196
  else {
197
197
  // --- GUEST MODE: SSE Proxy ---
198
198
  const guestId = CONFIG.instanceId;
199
+ let retryCount = 0;
200
+ const maxRetries = 50; // Prevent infinite reconnection loops
199
201
  // Random delay function to prevent thundering herd during re-election
200
202
  const randomDelay = () => Math.floor(Math.random() * 3000);
201
203
  const startProxy = () => {
204
+ if (retryCount >= maxRetries) {
205
+ console.error(`[Nexus Guest] Max retries (${maxRetries}) reached. Exiting.`);
206
+ process.exit(1);
207
+ }
208
+ retryCount++;
202
209
  // Clear any stale stdin listeners before starting
203
210
  process.stdin.removeAllListeners("data");
204
- console.error(`[Nexus:${guestId}] Global Hub detected at ${CONFIG.port}. Joining...`);
211
+ console.error(`[Nexus:${guestId}] Global Hub detected at ${CONFIG.port}. Joining... (attempt ${retryCount})`);
205
212
  let sessionId = null;
206
213
  let lastActivity = Date.now();
207
214
  // Watchdog: trigger re-election if Host is silent for too long
208
215
  const watchdog = setInterval(() => {
209
216
  if (Date.now() - lastActivity > 60000) {
210
- console.error("[Nexus Guest] Host stale. Triggering re-election...");
217
+ console.error("[Nexus Guest] Host stale. Reconnecting...");
211
218
  cleanup();
212
- // Random delay to prevent all guests from racing for the port
213
- setTimeout(() => this.run(), randomDelay());
219
+ // Use setImmediate to break call stack, then delay
220
+ setImmediate(() => setTimeout(startProxy, randomDelay()));
214
221
  }
215
222
  }, 10000);
216
223
  const cleanup = () => {
@@ -224,7 +231,7 @@ class NexusServer {
224
231
  const req = http.request({
225
232
  hostname: "127.0.0.1",
226
233
  port: CONFIG.port,
227
- path: `/mcp?sessionId=${sessionId}&id=${guestId}`,
234
+ path: `/mcp?sessionId=${sessionId}&id=${encodeURIComponent(guestId)}`,
228
235
  method: "POST",
229
236
  headers: { "Content-Type": "application/json" }
230
237
  });
@@ -236,7 +243,8 @@ class NexusServer {
236
243
  catch { /* suppress */ }
237
244
  };
238
245
  process.stdin.on("data", stdioHandler);
239
- http.get(`http://127.0.0.1:${CONFIG.port}/mcp?id=${guestId}`, (res) => {
246
+ http.get(`http://127.0.0.1:${CONFIG.port}/mcp?id=${encodeURIComponent(guestId)}`, (res) => {
247
+ retryCount = 0; // Reset on successful connection
240
248
  let buffer = "";
241
249
  res.on("data", (chunk) => {
242
250
  lastActivity = Date.now();
@@ -259,15 +267,15 @@ class NexusServer {
259
267
  }
260
268
  });
261
269
  res.on("end", () => {
262
- console.error("[Nexus Guest] Lost connection to Host. Re-electing...");
270
+ console.error("[Nexus Guest] Lost connection to Host. Reconnecting...");
263
271
  cleanup();
264
- // Random delay for re-election
265
- setTimeout(() => this.run(), randomDelay());
272
+ // Use setImmediate to break call stack
273
+ setImmediate(() => setTimeout(startProxy, randomDelay()));
266
274
  });
267
275
  }).on("error", () => {
268
- console.error("[Nexus Guest] Proxy Receive Error. Retry with random delay...");
276
+ console.error("[Nexus Guest] Proxy Receive Error. Retrying...");
269
277
  cleanup();
270
- setTimeout(() => this.run(), 1000 + randomDelay());
278
+ setImmediate(() => setTimeout(startProxy, 1000 + randomDelay()));
271
279
  });
272
280
  };
273
281
  startProxy();
@@ -1,6 +1,6 @@
1
- # Nexus 助手协作指令 (v0.2.1)
1
+ # Nexus 助手协作指令 (v0.3.0)
2
2
 
3
- 你现在是 **n2ns Nexus** 协作网络的一员。该系统集成了实时通信、结构化资产管理以及 **异步任务流 (Task Primitives)**,所有操作均落地在本地文件系统。
3
+ 你现在是 **n2ns Nexus** 协作网络的一员。该系统集成了实时通信、结构化资产管理、**异步任务流 (Task Primitives)** 以及 **全局 Hub 架构 (v0.3.0)**,所有操作均落地在本地文件系统。
4
4
 
5
5
  ## 🚦 核心原则:渐进式发现,增量读取
6
6
 
package/docs/README_zh.md CHANGED
@@ -102,36 +102,22 @@
102
102
 
103
103
  在你的 MCP 配置文件中(如 `claude_desktop_config.json` 或 Cursor MCP 设置)添加:
104
104
 
105
- #### 主导 AI
106
105
  ```json
107
106
  {
108
107
  "mcpServers": {
109
108
  "n2n-nexus": {
110
109
  "command": "npx",
111
- "args": [
112
- "-y",
113
- "@datafrog-io/n2n-nexus",
114
- "--root", "D:/DevSpace/Nexus_Storage"
115
- ]
110
+ "args": ["-y", "@datafrog-io/n2n-nexus"]
116
111
  }
117
112
  }
118
113
  }
119
114
  ```
120
115
 
121
- #### 协同 AI (Guest)
116
+ > **零配置**: 无需 `--id` 或 `--host`。直接运行即可协作!
117
+
118
+ **可选**: 使用 `--root` 指定自定义存储路径:
122
119
  ```json
123
- {
124
- "mcpServers": {
125
- "n2n-nexus": {
126
- "command": "npx",
127
- "args": [
128
- "-y",
129
- "@datafrog-io/n2n-nexus",
130
- "--root", "D:/DevSpace/Nexus_Storage"
131
- ]
132
- }
133
- }
134
- }
120
+ "args": ["-y", "@datafrog-io/n2n-nexus", "--root", "/path/to/storage"]
135
121
  ```
136
122
 
137
123
  ### 命令行参数
@@ -173,9 +159,11 @@ npm start -- --root ./my-storage
173
159
 
174
160
  ## ⭐ 支持本项目
175
161
 
176
- 如果 **n2ns Nexus** 帮助您构建了更好的 AI 工作流,考虑给我们一个 Star 吧!您的支持是我们持续改进的动力。
162
+ 如果 **n2ns Nexus** 帮助您构建了更好的 AI 工作流,考虑给我们一个 Star 吧!
177
163
 
178
- [![Star on GitHub](https://img.shields.io/github/stars/n2ns/n2n-nexus?style=social)](https://github.com/n2ns/n2n-nexus)
164
+ <a href="https://github.com/n2ns/n2n-nexus">
165
+ <img src="https://img.shields.io/github/stars/n2ns/n2n-nexus?style=for-the-badge&logo=github&logoColor=white&label=Star%20on%20GitHub" alt="Star on GitHub">
166
+ </a>
179
167
 
180
168
  ---
181
169
 
package/docs/TODO_zh.md CHANGED
@@ -69,3 +69,29 @@
69
69
 
70
70
  ---
71
71
  *最后更新日期: 2025-12-31 由 Nexus AI 助手更新。*
72
+
73
+ ---
74
+
75
+ ## 📅 2026-01-08: 全局 Hub 架构 (v0.3.0)
76
+
77
+ **目标**: 实现自动 Host 选举和零配置多 IDE 协作。
78
+
79
+ ### 全局 Hub 实现 (已完成)
80
+ - [x] **自动 Host 选举**: 基于端口 (5688-5700) 的选举,采用探测优先策略
81
+ - [x] **SSE 通讯**: Guest 实例的 Stdio-to-SSE 代理
82
+ - [x] **心跳与看门狗**: 30 秒 Host 心跳,60 秒 Guest 看门狗实现自动故障转移
83
+ - [x] **存储路径继承**: Guest 从 Host 继承 `rootStorage` 实现无缝故障转移
84
+ - [x] **术语重构**: 所有代码、测试和文档中 Moderator → Host
85
+
86
+ ### 文档更新 (已完成)
87
+ - [x] **README**: 添加全局 Hub 架构章节(含 ASCII 架构图)
88
+ - [x] **CHANGELOG**: 添加 v0.3.0 发布说明
89
+ - [x] **ASSISTANT_GUIDE**: 更新至 v0.3.0,包含全局 Hub 上下文
90
+ - [x] **零配置示例**: 简化 MCP 配置为单一 JSON 块
91
+
92
+ ### 测试覆盖 (已完成)
93
+ - [x] **election.test.ts**: 9 个测试用例,覆盖探测、绑定和竞争场景
94
+ - [x] **全部测试通过**: 55/55
95
+
96
+ ---
97
+ *最后更新: 2026-01-08 由 Antigravity AI 更新。*
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@datafrog-io/n2n-nexus",
3
- "version": "0.3.0",
4
- "description": "Unified Project Asset & Collaboration Hub (MCP Server) designed for AI agent coordination, featuring structured metadata, real-time messaging, and dependency topology.",
3
+ "version": "0.3.2",
4
+ "description": "Modular MCP Server for multi-AI assistant coordination",
5
5
  "main": "build/index.js",
6
6
  "type": "module",
7
7
  "bin": {