@datafrog-io/n2n-nexus 0.3.1 → 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/build/config.js +62 -58
- package/build/index.js +2 -2
- package/package.json +1 -1
package/build/config.js
CHANGED
|
@@ -104,70 +104,74 @@ async function probeHost(port) {
|
|
|
104
104
|
* 3. If not found, try to become Host
|
|
105
105
|
* 4. If bind fails, wait and re-probe (give winner time to start)
|
|
106
106
|
*/
|
|
107
|
-
async function isHostAutoElection(root
|
|
107
|
+
async function isHostAutoElection(root) {
|
|
108
108
|
const startPort = 5688;
|
|
109
109
|
const endPort = 5800;
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
for (let
|
|
116
|
-
|
|
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
|
+
}
|
|
117
126
|
}
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
resolve({ isHost: false });
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
server.listen(port, "127.0.0.1", () => {
|
|
150
|
-
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
|
+
});
|
|
151
155
|
});
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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)
|
|
161
166
|
}
|
|
162
|
-
//
|
|
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++;
|
|
163
174
|
}
|
|
164
|
-
// Fallback: All ports occupied - progressive backoff retry
|
|
165
|
-
// First 5 attempts: 1 minute interval, then 2 minute interval
|
|
166
|
-
const waitTime = retryCount < 5 ? 60000 : 120000;
|
|
167
|
-
const intervalStr = retryCount < 5 ? "1 minute" : "2 minutes";
|
|
168
|
-
console.error(`[Nexus] All ports ${startPort}-${endPort} occupied. Retry #${retryCount + 1} in ${intervalStr}...`);
|
|
169
|
-
await new Promise(r => setTimeout(r, waitTime));
|
|
170
|
-
return isHostAutoElection(root, retryCount + 1);
|
|
171
175
|
}
|
|
172
176
|
/**
|
|
173
177
|
* Automatic Project Name Detection
|
package/build/index.js
CHANGED
|
@@ -231,7 +231,7 @@ class NexusServer {
|
|
|
231
231
|
const req = http.request({
|
|
232
232
|
hostname: "127.0.0.1",
|
|
233
233
|
port: CONFIG.port,
|
|
234
|
-
path: `/mcp?sessionId=${sessionId}&id=${guestId}`,
|
|
234
|
+
path: `/mcp?sessionId=${sessionId}&id=${encodeURIComponent(guestId)}`,
|
|
235
235
|
method: "POST",
|
|
236
236
|
headers: { "Content-Type": "application/json" }
|
|
237
237
|
});
|
|
@@ -243,7 +243,7 @@ class NexusServer {
|
|
|
243
243
|
catch { /* suppress */ }
|
|
244
244
|
};
|
|
245
245
|
process.stdin.on("data", stdioHandler);
|
|
246
|
-
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
247
|
retryCount = 0; // Reset on successful connection
|
|
248
248
|
let buffer = "";
|
|
249
249
|
res.on("data", (chunk) => {
|