open-agents-ai 0.103.19 → 0.103.21
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 +40 -1
- package/dist/index.js +191 -35
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -919,6 +919,7 @@ The TUI features an animated multilingual phrase carousel, live metrics bar with
|
|
|
919
919
|
| `/models` | List all available models |
|
|
920
920
|
| `/endpoint <url>` | Connect to a remote vLLM or OpenAI-compatible API |
|
|
921
921
|
| `/endpoint <url> --auth <key>` | Set endpoint with Bearer auth |
|
|
922
|
+
| `/endpoint <peerId> --auth <key>` | Connect to a libp2p peer via nexus P2P network |
|
|
922
923
|
| **Task Control** | |
|
|
923
924
|
| `/pause` | Pause after current turn finishes (gentle halt) |
|
|
924
925
|
| `/stop` | Kill current inference immediately, save state |
|
|
@@ -952,7 +953,11 @@ The TUI features an animated multilingual phrase carousel, live metrics bar with
|
|
|
952
953
|
| `/secrets set <name> <value>` | Register a secret in the vault |
|
|
953
954
|
| `/secrets list` | List registered secrets (values hidden) |
|
|
954
955
|
| `/secrets import-env` | Auto-import secrets from environment variables |
|
|
955
|
-
| `/expose` | Expose local inference via
|
|
956
|
+
| `/expose ollama` | Expose local inference via libp2p (default) |
|
|
957
|
+
| `/expose ollama --tunnel` | Expose via cloudflared tunnel |
|
|
958
|
+
| `/expose ollama --full` | Allow full Ollama API access (pull/delete) |
|
|
959
|
+
| `/expose stop` | Stop the expose gateway |
|
|
960
|
+
| `/expose status` | Show expose usage stats |
|
|
956
961
|
| **Metrics & Updates** | |
|
|
957
962
|
| `/cost` | Show token cost breakdown for the current session |
|
|
958
963
|
| `/score` | Show inference capability scorecard (memory, compute, speed, model compatibility) |
|
|
@@ -1498,6 +1503,40 @@ Use `/endpoint` in the TUI or pass via CLI:
|
|
|
1498
1503
|
|
|
1499
1504
|
The agent auto-detects the provider, normalizes the URL (strips `/v1/chat/completions` if pasted), tests connectivity, and saves the configuration. You can paste full endpoint URLs — they'll be cleaned up automatically.
|
|
1500
1505
|
|
|
1506
|
+
### P2P Inference via libp2p
|
|
1507
|
+
|
|
1508
|
+
Expose your local Ollama models to the decentralized nexus network, or consume another peer's models — no port forwarding, DNS, or cloud accounts needed:
|
|
1509
|
+
|
|
1510
|
+
```bash
|
|
1511
|
+
# Provider: expose local models via libp2p (default transport)
|
|
1512
|
+
/expose ollama
|
|
1513
|
+
|
|
1514
|
+
# Output shows a copy-pasteable command:
|
|
1515
|
+
# /endpoint 12D3KooWSwaCi1J... --auth 5aJ68QuP...
|
|
1516
|
+
|
|
1517
|
+
# Consumer: connect to a remote peer
|
|
1518
|
+
/endpoint 12D3KooWSwaCi1JgXp2f2tQNFZFyMPZVcDe8oyTG672n6ELxSgBt --auth 5aJ68QuPxyz
|
|
1519
|
+
|
|
1520
|
+
# Fallback: expose via cloudflared tunnel instead
|
|
1521
|
+
/expose ollama --tunnel
|
|
1522
|
+
|
|
1523
|
+
# Grant full Ollama API access to consumers (pull, delete, etc.)
|
|
1524
|
+
/expose ollama --full
|
|
1525
|
+
```
|
|
1526
|
+
|
|
1527
|
+
Transport: DHT + mDNS + NATS relay + circuit relay. Auth key is auto-generated and required for all requests. System metrics (CPU/GPU/memory) are available to consumers via the `system_metrics` capability. Without `--full`, destructive Ollama API endpoints (`/api/pull`, `/api/delete`, `/api/create`) are blocked.
|
|
1528
|
+
|
|
1529
|
+
### Endpoint Cascade Failover
|
|
1530
|
+
|
|
1531
|
+
When you've used multiple endpoints, the agent automatically builds a failover cascade. If the primary endpoint fails with transient errors (502, connection refused, timeout), it transparently switches to the next endpoint that has the same model — then periodically probes the primary to return when it recovers:
|
|
1532
|
+
|
|
1533
|
+
```
|
|
1534
|
+
[cascade] Failover → https://api.groq.com/openai: 2 consecutive failures: fetch failed
|
|
1535
|
+
[cascade] Primary recovered: http://localhost:11434
|
|
1536
|
+
```
|
|
1537
|
+
|
|
1538
|
+
No configuration needed — the cascade is built from your endpoint usage history. Works across local Ollama, cloud providers, and P2P peers.
|
|
1539
|
+
|
|
1501
1540
|
## Evaluation Suite
|
|
1502
1541
|
|
|
1503
1542
|
46 evaluation tasks test the agent's autonomous capabilities across coding, web research, SDLC analysis, tool creation, multi-file reasoning, memory systems, and context engineering:
|
package/dist/index.js
CHANGED
|
@@ -27395,7 +27395,34 @@ async function collectSystemMetricsAsync() {
|
|
|
27395
27395
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
27396
27396
|
};
|
|
27397
27397
|
}
|
|
27398
|
-
|
|
27398
|
+
function readP2PExposeState(stateDir) {
|
|
27399
|
+
try {
|
|
27400
|
+
const path = join35(stateDir, P2P_STATE_FILE_NAME);
|
|
27401
|
+
if (!existsSync27(path))
|
|
27402
|
+
return null;
|
|
27403
|
+
const raw = readFileSync19(path, "utf8");
|
|
27404
|
+
const data = JSON.parse(raw);
|
|
27405
|
+
if (!data.peerId || !data.authKey)
|
|
27406
|
+
return null;
|
|
27407
|
+
return data;
|
|
27408
|
+
} catch {
|
|
27409
|
+
return null;
|
|
27410
|
+
}
|
|
27411
|
+
}
|
|
27412
|
+
function writeP2PExposeState(stateDir, state) {
|
|
27413
|
+
try {
|
|
27414
|
+
mkdirSync9(stateDir, { recursive: true });
|
|
27415
|
+
writeFileSync9(join35(stateDir, P2P_STATE_FILE_NAME), JSON.stringify(state, null, 2));
|
|
27416
|
+
} catch {
|
|
27417
|
+
}
|
|
27418
|
+
}
|
|
27419
|
+
function removeP2PExposeState(stateDir) {
|
|
27420
|
+
try {
|
|
27421
|
+
unlinkSync3(join35(stateDir, P2P_STATE_FILE_NAME));
|
|
27422
|
+
} catch {
|
|
27423
|
+
}
|
|
27424
|
+
}
|
|
27425
|
+
var HOP_BY_HOP_HEADERS, CF_HEADERS_PREFIX, DEFAULT_TARGETS, STATE_FILE_NAME, ExposeGateway, P2P_STATE_FILE_NAME, ExposeP2PGateway;
|
|
27399
27426
|
var init_expose = __esm({
|
|
27400
27427
|
"packages/cli/dist/tui/expose.js"() {
|
|
27401
27428
|
"use strict";
|
|
@@ -28047,7 +28074,8 @@ ${this.formatConnectionInfo()}`);
|
|
|
28047
28074
|
return lines.join("\n");
|
|
28048
28075
|
}
|
|
28049
28076
|
};
|
|
28050
|
-
|
|
28077
|
+
P2P_STATE_FILE_NAME = "expose-p2p-state.json";
|
|
28078
|
+
ExposeP2PGateway = class _ExposeP2PGateway extends EventEmitter3 {
|
|
28051
28079
|
_nexusTool;
|
|
28052
28080
|
// NexusTool instance
|
|
28053
28081
|
_kind;
|
|
@@ -28156,6 +28184,16 @@ ${this.formatConnectionInfo()}`);
|
|
|
28156
28184
|
this._stats.status = "active";
|
|
28157
28185
|
this._stats.startedAt = Date.now();
|
|
28158
28186
|
this.emitStats();
|
|
28187
|
+
if (this._stateDir && this._peerId) {
|
|
28188
|
+
writeP2PExposeState(this._stateDir, {
|
|
28189
|
+
peerId: this._peerId,
|
|
28190
|
+
authKey: this._authKey,
|
|
28191
|
+
kind: this._kind,
|
|
28192
|
+
targetUrl: this._targetUrl,
|
|
28193
|
+
margin: this._margin,
|
|
28194
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
28195
|
+
});
|
|
28196
|
+
}
|
|
28159
28197
|
this.startMeteringPoll(nexusDir);
|
|
28160
28198
|
return this._peerId ?? "connected (peer ID pending)";
|
|
28161
28199
|
}
|
|
@@ -28164,6 +28202,54 @@ ${this.formatConnectionInfo()}`);
|
|
|
28164
28202
|
this._stats.status = "standby";
|
|
28165
28203
|
this._stats.activeConnections = 0;
|
|
28166
28204
|
this.emitStats();
|
|
28205
|
+
if (this._stateDir)
|
|
28206
|
+
removeP2PExposeState(this._stateDir);
|
|
28207
|
+
}
|
|
28208
|
+
/**
|
|
28209
|
+
* Check for and reconnect to a surviving P2P expose from a previous session.
|
|
28210
|
+
* Verifies the nexus daemon is still running and the peer ID is valid,
|
|
28211
|
+
* then resumes metering poll without re-registering capabilities
|
|
28212
|
+
* (the daemon keeps them registered across OA restarts).
|
|
28213
|
+
*/
|
|
28214
|
+
static async checkAndReconnect(stateDir, nexusTool, options) {
|
|
28215
|
+
const state = readP2PExposeState(stateDir);
|
|
28216
|
+
if (!state)
|
|
28217
|
+
return null;
|
|
28218
|
+
const nexusDir = nexusTool.getNexusDir();
|
|
28219
|
+
const statusPath = join35(nexusDir, "status.json");
|
|
28220
|
+
try {
|
|
28221
|
+
if (!existsSync27(statusPath)) {
|
|
28222
|
+
removeP2PExposeState(stateDir);
|
|
28223
|
+
return null;
|
|
28224
|
+
}
|
|
28225
|
+
const status = JSON.parse(readFileSync19(statusPath, "utf8"));
|
|
28226
|
+
if (!status.connected || !status.peerId) {
|
|
28227
|
+
removeP2PExposeState(stateDir);
|
|
28228
|
+
return null;
|
|
28229
|
+
}
|
|
28230
|
+
if (status.peerId !== state.peerId) {
|
|
28231
|
+
removeP2PExposeState(stateDir);
|
|
28232
|
+
return null;
|
|
28233
|
+
}
|
|
28234
|
+
} catch {
|
|
28235
|
+
removeP2PExposeState(stateDir);
|
|
28236
|
+
return null;
|
|
28237
|
+
}
|
|
28238
|
+
const gateway = new _ExposeP2PGateway({
|
|
28239
|
+
kind: state.kind,
|
|
28240
|
+
targetUrl: state.targetUrl,
|
|
28241
|
+
authKey: state.authKey,
|
|
28242
|
+
stateDir,
|
|
28243
|
+
margin: state.margin,
|
|
28244
|
+
...options
|
|
28245
|
+
}, nexusTool);
|
|
28246
|
+
gateway._peerId = state.peerId;
|
|
28247
|
+
gateway._stats.status = "active";
|
|
28248
|
+
gateway._stats.startedAt = new Date(state.startedAt).getTime();
|
|
28249
|
+
gateway.emitStats();
|
|
28250
|
+
gateway.startMeteringPoll(nexusDir);
|
|
28251
|
+
options.onInfo?.(`P2P expose reconnected: ${state.peerId}`);
|
|
28252
|
+
return gateway;
|
|
28167
28253
|
}
|
|
28168
28254
|
/** Poll the daemon's metering.jsonl and status.json for stats */
|
|
28169
28255
|
startMeteringPoll(nexusDir) {
|
|
@@ -32979,10 +33065,20 @@ async function handleSlashCommand(input, ctx) {
|
|
|
32979
33065
|
renderWarning("Expose gateway not available in this context.");
|
|
32980
33066
|
return "handled";
|
|
32981
33067
|
}
|
|
32982
|
-
if (arg
|
|
33068
|
+
if (arg?.startsWith("stop") || arg === "off") {
|
|
33069
|
+
const stopParts = (arg ?? "").split(/\s+/);
|
|
33070
|
+
let stopWhich;
|
|
33071
|
+
if (stopParts.includes("--tunnel"))
|
|
33072
|
+
stopWhich = "tunnel";
|
|
33073
|
+
else if (stopParts.includes("--libp2p"))
|
|
33074
|
+
stopWhich = "libp2p";
|
|
32983
33075
|
if (ctx.isExposeActive?.()) {
|
|
32984
|
-
await ctx.exposeStop?.();
|
|
32985
|
-
|
|
33076
|
+
await ctx.exposeStop?.(stopWhich);
|
|
33077
|
+
if (stopWhich) {
|
|
33078
|
+
renderInfo(`Expose ${stopWhich} gateway stopped.`);
|
|
33079
|
+
} else {
|
|
33080
|
+
renderInfo("All expose gateways stopped.");
|
|
33081
|
+
}
|
|
32986
33082
|
} else {
|
|
32987
33083
|
renderWarning("No active expose gateway.");
|
|
32988
33084
|
}
|
|
@@ -33009,7 +33105,9 @@ async function handleSlashCommand(input, ctx) {
|
|
|
33009
33105
|
renderInfo(" /expose vllm Expose local vLLM (8000)");
|
|
33010
33106
|
renderInfo(" /expose llvm Expose local LLVM (8080)");
|
|
33011
33107
|
renderInfo(" /expose host:port Expose custom address");
|
|
33012
|
-
renderInfo(" /expose stop Stop
|
|
33108
|
+
renderInfo(" /expose stop Stop all gateways");
|
|
33109
|
+
renderInfo(" /expose stop --tunnel Stop tunnel only");
|
|
33110
|
+
renderInfo(" /expose stop --libp2p Stop libp2p only");
|
|
33013
33111
|
renderInfo(" /expose status Show usage stats");
|
|
33014
33112
|
renderInfo(" --key <key> Use specific auth key (tunnel mode)");
|
|
33015
33113
|
renderInfo(" --key Auto-generate auth key (tunnel mode)");
|
|
@@ -33017,9 +33115,6 @@ async function handleSlashCommand(input, ctx) {
|
|
|
33017
33115
|
}
|
|
33018
33116
|
return "handled";
|
|
33019
33117
|
}
|
|
33020
|
-
if (ctx.isExposeActive?.()) {
|
|
33021
|
-
await ctx.exposeStop?.();
|
|
33022
|
-
}
|
|
33023
33118
|
const exposeParts = arg.split(/\s+/);
|
|
33024
33119
|
const kindOrUrl = exposeParts[0];
|
|
33025
33120
|
let exposeAuthKey;
|
|
@@ -44437,11 +44532,15 @@ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce
|
|
|
44437
44532
|
}
|
|
44438
44533
|
try {
|
|
44439
44534
|
const endpointHistory = loadUsageHistory("endpoint", repoRoot);
|
|
44535
|
+
const primaryIsNexus = config.backendType === "nexus";
|
|
44440
44536
|
const fallbackEndpoints = [];
|
|
44441
44537
|
for (const entry of endpointHistory) {
|
|
44442
44538
|
if (entry.value === config.backendUrl)
|
|
44443
44539
|
continue;
|
|
44444
44540
|
const bt = entry.meta?.backendType ?? "ollama";
|
|
44541
|
+
const entryIsNexus = bt === "nexus";
|
|
44542
|
+
if (primaryIsNexus !== entryIsNexus)
|
|
44543
|
+
continue;
|
|
44445
44544
|
const ep = {
|
|
44446
44545
|
url: entry.value,
|
|
44447
44546
|
backendType: bt,
|
|
@@ -45104,7 +45203,8 @@ async function startInteractive(config, repoPath) {
|
|
|
45104
45203
|
autoUpdateTimer.unref();
|
|
45105
45204
|
const voiceEngine = new VoiceEngine();
|
|
45106
45205
|
let voiceSession = null;
|
|
45107
|
-
let
|
|
45206
|
+
let tunnelGateway = null;
|
|
45207
|
+
let p2pGateway = null;
|
|
45108
45208
|
let peerMesh = null;
|
|
45109
45209
|
let inferenceRouter = null;
|
|
45110
45210
|
const secretVault = new SecretVault(join50(repoRoot, ".oa", "vault.enc"));
|
|
@@ -45456,7 +45556,7 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
|
|
|
45456
45556
|
onError: (msg) => writeContent(() => renderWarning(msg))
|
|
45457
45557
|
});
|
|
45458
45558
|
if (reconnected) {
|
|
45459
|
-
|
|
45559
|
+
tunnelGateway = reconnected;
|
|
45460
45560
|
reconnected.on("stats", (stats) => {
|
|
45461
45561
|
statusBar.setExposeStatus({
|
|
45462
45562
|
status: stats.status,
|
|
@@ -45471,6 +45571,28 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
|
|
|
45471
45571
|
}
|
|
45472
45572
|
} catch {
|
|
45473
45573
|
}
|
|
45574
|
+
try {
|
|
45575
|
+
const oaDir = join50(repoRoot, ".oa");
|
|
45576
|
+
const reconnectedP2P = await ExposeP2PGateway.checkAndReconnect(oaDir, new NexusTool(repoRoot), {
|
|
45577
|
+
onInfo: (msg) => writeContent(() => renderInfo(msg)),
|
|
45578
|
+
onError: (msg) => writeContent(() => renderWarning(msg))
|
|
45579
|
+
});
|
|
45580
|
+
if (reconnectedP2P) {
|
|
45581
|
+
p2pGateway = reconnectedP2P;
|
|
45582
|
+
reconnectedP2P.on("stats", (stats) => {
|
|
45583
|
+
statusBar.setExposeStatus({
|
|
45584
|
+
status: stats.status,
|
|
45585
|
+
totalRequests: stats.totalRequests,
|
|
45586
|
+
activeConnections: stats.activeConnections,
|
|
45587
|
+
modelUsage: stats.modelUsage
|
|
45588
|
+
});
|
|
45589
|
+
});
|
|
45590
|
+
writeContent(() => {
|
|
45591
|
+
renderInfo(`Reconnected to existing P2P expose: ${reconnectedP2P.peerId}`);
|
|
45592
|
+
});
|
|
45593
|
+
}
|
|
45594
|
+
} catch {
|
|
45595
|
+
}
|
|
45474
45596
|
try {
|
|
45475
45597
|
const isPeer = currentConfig.backendUrl.startsWith("peer://");
|
|
45476
45598
|
const isRemote = !currentConfig.backendUrl.includes("127.0.0.1") && !currentConfig.backendUrl.includes("localhost") && !isPeer;
|
|
@@ -46123,14 +46245,18 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
46123
46245
|
}
|
|
46124
46246
|
const effectiveTransport = transport ?? "libp2p";
|
|
46125
46247
|
if (effectiveTransport === "libp2p") {
|
|
46248
|
+
if (p2pGateway?.isActive) {
|
|
46249
|
+
await p2pGateway.stop();
|
|
46250
|
+
p2pGateway = null;
|
|
46251
|
+
}
|
|
46126
46252
|
const nexusTool = new NexusTool(repoRoot);
|
|
46127
|
-
const
|
|
46253
|
+
const newP2P = new ExposeP2PGateway({
|
|
46128
46254
|
kind,
|
|
46129
46255
|
targetUrl,
|
|
46130
46256
|
authKey,
|
|
46131
46257
|
stateDir: join50(repoRoot, ".oa")
|
|
46132
46258
|
}, nexusTool);
|
|
46133
|
-
|
|
46259
|
+
newP2P.on("stats", (stats) => {
|
|
46134
46260
|
statusBar.setExposeStatus({
|
|
46135
46261
|
status: stats.status,
|
|
46136
46262
|
totalRequests: stats.totalRequests,
|
|
@@ -46139,10 +46265,10 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
46139
46265
|
});
|
|
46140
46266
|
});
|
|
46141
46267
|
try {
|
|
46142
|
-
const peerId = await
|
|
46143
|
-
|
|
46268
|
+
const peerId = await newP2P.start();
|
|
46269
|
+
p2pGateway = newP2P;
|
|
46144
46270
|
writeContent(() => {
|
|
46145
|
-
process.stdout.write("\n" +
|
|
46271
|
+
process.stdout.write("\n" + newP2P.formatConnectionInfo() + "\n\n");
|
|
46146
46272
|
});
|
|
46147
46273
|
return peerId;
|
|
46148
46274
|
} catch (err) {
|
|
@@ -46156,8 +46282,12 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
46156
46282
|
}
|
|
46157
46283
|
}
|
|
46158
46284
|
}
|
|
46159
|
-
|
|
46160
|
-
|
|
46285
|
+
if (tunnelGateway?.isActive) {
|
|
46286
|
+
await tunnelGateway.stop();
|
|
46287
|
+
tunnelGateway = null;
|
|
46288
|
+
}
|
|
46289
|
+
const newTunnel = new ExposeGateway({ kind, targetUrl, authKey, fullAccess, stateDir: join50(repoRoot, ".oa") });
|
|
46290
|
+
newTunnel.on("stats", (stats) => {
|
|
46161
46291
|
statusBar.setExposeStatus({
|
|
46162
46292
|
status: stats.status,
|
|
46163
46293
|
totalRequests: stats.totalRequests,
|
|
@@ -46166,41 +46296,67 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
46166
46296
|
});
|
|
46167
46297
|
});
|
|
46168
46298
|
try {
|
|
46169
|
-
const url = await
|
|
46170
|
-
|
|
46299
|
+
const url = await newTunnel.start();
|
|
46300
|
+
tunnelGateway = newTunnel;
|
|
46171
46301
|
writeContent(() => {
|
|
46172
|
-
process.stdout.write("\n" +
|
|
46302
|
+
process.stdout.write("\n" + newTunnel.formatConnectionInfo() + "\n\n");
|
|
46173
46303
|
});
|
|
46174
46304
|
return url;
|
|
46175
46305
|
} catch (err) {
|
|
46176
|
-
exposeGateway = null;
|
|
46177
46306
|
throw err;
|
|
46178
46307
|
}
|
|
46179
46308
|
},
|
|
46180
|
-
async exposeStop() {
|
|
46181
|
-
if (
|
|
46182
|
-
|
|
46309
|
+
async exposeStop(which) {
|
|
46310
|
+
if (!which || which === "tunnel") {
|
|
46311
|
+
if (tunnelGateway) {
|
|
46312
|
+
await tunnelGateway.stop();
|
|
46313
|
+
tunnelGateway = null;
|
|
46314
|
+
}
|
|
46315
|
+
}
|
|
46316
|
+
if (!which || which === "libp2p") {
|
|
46317
|
+
if (p2pGateway) {
|
|
46318
|
+
await p2pGateway.stop();
|
|
46319
|
+
p2pGateway = null;
|
|
46320
|
+
}
|
|
46321
|
+
}
|
|
46322
|
+
if (!tunnelGateway?.isActive && !p2pGateway?.isActive) {
|
|
46183
46323
|
statusBar.clearExposeStatus();
|
|
46184
|
-
exposeGateway = null;
|
|
46185
46324
|
}
|
|
46186
46325
|
},
|
|
46187
46326
|
isExposeActive() {
|
|
46188
|
-
return
|
|
46327
|
+
return (tunnelGateway?.isActive ?? false) || (p2pGateway?.isActive ?? false);
|
|
46189
46328
|
},
|
|
46190
46329
|
getExposeInfo() {
|
|
46191
|
-
|
|
46330
|
+
const infos = [];
|
|
46331
|
+
if (tunnelGateway?.isActive) {
|
|
46332
|
+
infos.push({
|
|
46333
|
+
tunnelUrl: tunnelGateway.tunnelUrl,
|
|
46334
|
+
authKey: tunnelGateway.authKey,
|
|
46335
|
+
stats: tunnelGateway.formatStats(),
|
|
46336
|
+
transport: "tunnel"
|
|
46337
|
+
});
|
|
46338
|
+
}
|
|
46339
|
+
if (p2pGateway?.isActive) {
|
|
46340
|
+
infos.push({
|
|
46341
|
+
tunnelUrl: p2pGateway.peerId,
|
|
46342
|
+
authKey: p2pGateway.authKey,
|
|
46343
|
+
stats: p2pGateway.formatStats(),
|
|
46344
|
+
transport: "libp2p"
|
|
46345
|
+
});
|
|
46346
|
+
}
|
|
46347
|
+
if (infos.length === 0)
|
|
46192
46348
|
return null;
|
|
46193
|
-
if (
|
|
46349
|
+
if (infos.length === 2) {
|
|
46194
46350
|
return {
|
|
46195
|
-
tunnelUrl:
|
|
46196
|
-
authKey:
|
|
46197
|
-
stats:
|
|
46351
|
+
tunnelUrl: infos[0].tunnelUrl,
|
|
46352
|
+
authKey: infos[0].authKey,
|
|
46353
|
+
stats: infos[0].stats + "\n\n" + infos[1].stats
|
|
46198
46354
|
};
|
|
46199
46355
|
}
|
|
46200
46356
|
return {
|
|
46201
|
-
tunnelUrl:
|
|
46202
|
-
authKey:
|
|
46203
|
-
stats:
|
|
46357
|
+
tunnelUrl: infos[0].tunnelUrl,
|
|
46358
|
+
authKey: infos[0].authKey,
|
|
46359
|
+
stats: infos[0].stats
|
|
46204
46360
|
};
|
|
46205
46361
|
},
|
|
46206
46362
|
// ── Nexus daemon connect ──────────────────────────────────────────────
|
package/package.json
CHANGED