@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 +9 -21
- package/build/config.js +62 -46
- package/build/index.js +19 -11
- package/docs/ASSISTANT_GUIDE.md +2 -2
- package/docs/README_zh.md +9 -21
- package/docs/TODO_zh.md +26 -0
- package/package.json +2 -2
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
|
-
|
|
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!
|
|
163
|
+
If **n2ns Nexus** helps you build better AI workflows, consider giving it a star!
|
|
178
164
|
|
|
179
|
-
|
|
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 =
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
//
|
|
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.
|
|
217
|
+
console.error("[Nexus Guest] Host stale. Reconnecting...");
|
|
211
218
|
cleanup();
|
|
212
|
-
//
|
|
213
|
-
|
|
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.
|
|
270
|
+
console.error("[Nexus Guest] Lost connection to Host. Reconnecting...");
|
|
263
271
|
cleanup();
|
|
264
|
-
//
|
|
265
|
-
|
|
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.
|
|
276
|
+
console.error("[Nexus Guest] Proxy Receive Error. Retrying...");
|
|
269
277
|
cleanup();
|
|
270
|
-
|
|
278
|
+
setImmediate(() => setTimeout(startProxy, 1000 + randomDelay()));
|
|
271
279
|
});
|
|
272
280
|
};
|
|
273
281
|
startProxy();
|
package/docs/ASSISTANT_GUIDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Nexus 助手协作指令 (v0.
|
|
1
|
+
# Nexus 助手协作指令 (v0.3.0)
|
|
2
2
|
|
|
3
|
-
你现在是 **n2ns Nexus**
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
4
|
-
"description": "
|
|
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": {
|