@nekzus/liop 1.2.0-alpha.9 → 1.3.0-alpha.1
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 +59 -47
- package/dist/bin/agent.js +222 -51
- package/dist/bridge/index.js +7 -6
- package/dist/bridge/stream.js +23 -15
- package/dist/client/index.js +53 -42
- package/dist/crypto/logic-image-id.d.ts +3 -0
- package/dist/crypto/logic-image-id.js +27 -0
- package/dist/crypto/verifier.d.ts +1 -1
- package/dist/crypto/verifier.js +9 -20
- package/dist/economy/estimator.d.ts +53 -0
- package/dist/economy/estimator.js +69 -0
- package/dist/economy/index.d.ts +5 -0
- package/dist/economy/index.js +3 -0
- package/dist/economy/otel.d.ts +38 -0
- package/dist/economy/otel.js +100 -0
- package/dist/economy/telemetry.d.ts +77 -0
- package/dist/economy/telemetry.js +224 -0
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +19 -0
- package/dist/gateway/hybrid.d.ts +3 -1
- package/dist/gateway/hybrid.js +38 -13
- package/dist/gateway/router.d.ts +25 -9
- package/dist/gateway/router.js +484 -133
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/mesh/node.d.ts +16 -0
- package/dist/mesh/node.js +394 -113
- package/dist/prompts/adapters.d.ts +16 -0
- package/dist/prompts/adapters.js +55 -0
- package/dist/rpc/proto.js +2 -1
- package/dist/rpc/server.d.ts +1 -1
- package/dist/rpc/server.js +4 -3
- package/dist/rpc/tls.js +3 -2
- package/dist/sandbox/guardian.js +27 -4
- package/dist/sandbox/wasi.d.ts +1 -1
- package/dist/sandbox/wasi.js +44 -3
- package/dist/security/guardian.js +3 -2
- package/dist/security/zk.d.ts +3 -4
- package/dist/security/zk.js +33 -10
- package/dist/server/index.d.ts +53 -4
- package/dist/server/index.js +362 -49
- package/dist/server/pii.d.ts +12 -0
- package/dist/server/pii.js +90 -0
- package/dist/types.d.ts +16 -0
- package/dist/utils/logger.d.ts +21 -0
- package/dist/utils/logger.js +70 -0
- package/dist/utils/mcpCompact.d.ts +11 -0
- package/dist/utils/mcpCompact.js +29 -0
- package/dist/workers/logic-execution.d.ts +1 -1
- package/dist/workers/logic-execution.js +42 -22
- package/dist/workers/zk-verifier.d.ts +2 -0
- package/dist/workers/zk-verifier.js +52 -34
- package/package.json +16 -4
package/README.md
CHANGED
|
@@ -4,19 +4,19 @@
|
|
|
4
4
|
<img alt="Logic-Injection-on-Origin Protocol Logo" src="https://res.cloudinary.com/dsvsl0b0b/image/upload/v1774702621/Neural-Mesh-Protocol/hoanw0m6tybpz5fbl12n.svg?v=20260328" width="700">
|
|
5
5
|
</picture>
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
<h1>Logic-Injection-on-Origin Protocol (LIOP) — TypeScript SDK</h1>
|
|
8
8
|
<p align="center">
|
|
9
|
-
<a href="https://github.com/Nekzus/
|
|
9
|
+
<a href="https://github.com/Nekzus/LIOP/actions/workflows/ci.yml"><img src="https://github.com/Nekzus/LIOP/actions/workflows/ci.yml/badge.svg?event=push" alt="Github Workflow"></a>
|
|
10
10
|
<a href="https://www.npmjs.com/package/@nekzus/liop"><img src="https://img.shields.io/npm/v/@nekzus/liop.svg" alt="npm version"></a>
|
|
11
11
|
<a href="https://www.npmjs.com/package/@nekzus/liop"><img src="https://img.shields.io/npm/dm/@nekzus/liop.svg" alt="npm-month"></a>
|
|
12
12
|
<a href="https://www.npmjs.com/package/@nekzus/liop"><img src="https://img.shields.io/npm/dt/@nekzus/liop.svg?style=flat" alt="npm-total"></a>
|
|
13
|
-
<a href="https://github.com/Nekzus/
|
|
14
|
-
<a href="https://
|
|
13
|
+
<a href="https://github.com/Nekzus/LIOP/blob/main/LICENSE"><img src="https://img.shields.io/github/license/Nekzus/LIOP.svg" alt="License"></a>
|
|
14
|
+
<a href="https://nekzus-32.mintlify.app/"><img src="https://img.shields.io/badge/docs-mintlify-0D9373?style=flat" alt="Docs"></a>
|
|
15
15
|
<a href="https://deepwiki.com/Nekzus/LIOP"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
|
|
16
16
|
<a href="https://paypal.me/maseortega"><img src="https://img.shields.io/badge/donate-paypal-blue.svg?style=flat-square" alt="Donate"></a>
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
<p><strong>The official TypeScript SDK for the Logic-Injection-on-Origin Protocol.</strong></p>
|
|
20
20
|
<p>Deploy Logic-on-Origin with WebAssembly sandboxing, gRPC-speed execution, and full MCP backward compatibility.</p>
|
|
21
21
|
</div>
|
|
22
22
|
|
|
@@ -30,18 +30,19 @@ This fundamentally solves the data privacy, bandwidth, and latency challenges of
|
|
|
30
30
|
|
|
31
31
|
### Key Capabilities
|
|
32
32
|
|
|
33
|
-
| Feature
|
|
34
|
-
|
|
35
|
-
| **Logic-Injection-on-Origin** | LLMs send code, not queries. Data never leaves the origin server.
|
|
36
|
-
| **MCP Drop-in Replacement**
|
|
37
|
-
| **Guardian AST**
|
|
38
|
-
| **WASI Sandbox**
|
|
39
|
-
| **PII Shield**
|
|
40
|
-
| **ZK-Receipts**
|
|
41
|
-
| **Worker Pool**
|
|
42
|
-
| **
|
|
43
|
-
| **
|
|
44
|
-
| **
|
|
33
|
+
| Feature | Description |
|
|
34
|
+
| :---------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
35
|
+
| **Logic-Injection-on-Origin** | LLMs send code, not queries. Data never leaves the origin server. |
|
|
36
|
+
| **MCP Drop-in Replacement** | `LiopServer` mirrors the Anthropic MCP `Server` API — tools, resources, and prompts with `Zod` schemas. |
|
|
37
|
+
| **Guardian AST** | Zero-time heuristic inspection blocks sandbox escapes (`require`, `fs`, `eval`, `fetch`, prototype pollution). |
|
|
38
|
+
| **WASI Sandbox** | JavaScript payloads execute inside V8 isolates with CPU fuel limits and no access to Node.js globals. |
|
|
39
|
+
| **PII Shield** | Multi-layer egress filter with Regional Presets (Email, Credit Card with Luhn, IP, Phone, SSN, IBAN Mod-97, Passport MRZ) and custom keys. |
|
|
40
|
+
| **ZK-Receipts** | Cryptographic proof (SHA-256 ImageID + HMAC-SHA256 seal) that the returned result was computed honestly from the injected logic. |
|
|
41
|
+
| **Worker Pool** | Heavy computation (crypto, sandboxing) dispatched to OS threads via `piscina`, unblocking the V8 event loop. |
|
|
42
|
+
| **Cross-AI Adapters** | Zero-Shot system prompts automatically adapt instructions for Claude (XML-heavy) vs OpenAI/Gemini (JSON-schema). |
|
|
43
|
+
| **MCP Bridge** | `LiopMcpBridge` adapts any `LiopServer` to the JSON-RPC 2.0 / stdio protocol used by Claude Desktop, Cursor, etc. |
|
|
44
|
+
| **Post-Quantum Ready** | ML-KEM-768 (Kyber) handshake + AES-256-GCM symmetric encryption for transport-layer security. |
|
|
45
|
+
| **P2P Mesh** | Kademlia DHT discovery via `libp2p` with TCP + WebSocket + Yamux multiplexing and Noise encryption. |
|
|
45
46
|
|
|
46
47
|
---
|
|
47
48
|
|
|
@@ -90,6 +91,7 @@ To use the Neural Mesh inside Claude Desktop, update your `claude_desktop_config
|
|
|
90
91
|
### Persistence & Identity
|
|
91
92
|
|
|
92
93
|
The agent automatically manages your P2P identity:
|
|
94
|
+
|
|
93
95
|
- **Identity Path**: `~/.liop/identity.json`. This file contains your unique PeerID. Keep it safe if you want to maintain a consistent identity in the mesh.
|
|
94
96
|
- **Bootstrap Nodes**: By default, the agent connects to the **LIOP Alpha Nexus**. You can provide custom bootstrap addresses as CLI arguments:
|
|
95
97
|
```bash
|
|
@@ -196,24 +198,24 @@ new LiopServer(
|
|
|
196
198
|
|
|
197
199
|
#### Methods
|
|
198
200
|
|
|
199
|
-
| Method
|
|
200
|
-
|
|
201
|
-
| `tool()`
|
|
202
|
-
| `prompt()`
|
|
203
|
-
| `resource()`
|
|
204
|
-
| `dataDictionary()`
|
|
205
|
-
| `setSandboxData()`
|
|
206
|
-
| `enableZeroShotAutonomy()` | `()`
|
|
207
|
-
| `callTool()`
|
|
208
|
-
| `listTools()`
|
|
209
|
-
| `listPrompts()`
|
|
210
|
-
| `getPrompt()`
|
|
211
|
-
| `listResources()`
|
|
212
|
-
| `readResource()`
|
|
213
|
-
| `getServerInfo()`
|
|
214
|
-
| `connectToMesh()`
|
|
215
|
-
| `clearAstCache()`
|
|
216
|
-
| `close()`
|
|
201
|
+
| Method | Signature | Description |
|
|
202
|
+
| :--------------------------- | :------------------------------------------------- | :---------------------------------------------------------------------------------- |
|
|
203
|
+
| `tool()` | `(name, description, zodSchema, handler)` | Registers a callable tool with Zod input validation. |
|
|
204
|
+
| `prompt()` | `(name, description, args, handler)` | Registers a dynamic prompt template. |
|
|
205
|
+
| `resource()` | `(name, uri, description?, mimeType?, content?)` | Registers a readable resource. |
|
|
206
|
+
| `dataDictionary()` | `(schema, name?, uri?, description?)` | Broadcasts a data schema so LLMs can write accurate Logic-Injection-on-Origin code. |
|
|
207
|
+
| `setSandboxData()` | `(records: Record[])` | Injects data into the sandbox as `env.records` for Logic-on-Origin tools. |
|
|
208
|
+
| `enableZeroShotAutonomy()` | `()` | Registers the "Blind Analyst" prompt for autonomous code generation. |
|
|
209
|
+
| `callTool()` | `(request: CallToolRequest)` | Invokes a registered tool (used locally or via MCP Bridge). |
|
|
210
|
+
| `listTools()` | `()` | Returns all registered tools. |
|
|
211
|
+
| `listPrompts()` | `()` | Returns all registered prompts. |
|
|
212
|
+
| `getPrompt()` | `(request: GetPromptRequest)` | Returns a specific prompt by name. |
|
|
213
|
+
| `listResources()` | `()` | Returns all registered resources. |
|
|
214
|
+
| `readResource()` | `(uri: string)` | Reads a resource by URI. |
|
|
215
|
+
| `getServerInfo()` | `()` | Returns the server's name and version. |
|
|
216
|
+
| `connectToMesh()` | `()` | Connects to the libp2p Kademlia DHT. |
|
|
217
|
+
| `clearAstCache()` | `()` | Invalidates the Guardian AST logic cache. |
|
|
218
|
+
| `close()` | `()` | Destroys the worker pool and releases threads. |
|
|
217
219
|
|
|
218
220
|
### `LiopMcpBridge`
|
|
219
221
|
|
|
@@ -225,6 +227,7 @@ await bridge.connect();
|
|
|
225
227
|
```
|
|
226
228
|
|
|
227
229
|
**Supported JSON-RPC methods:**
|
|
230
|
+
|
|
228
231
|
- `initialize` — Returns server capabilities and info
|
|
229
232
|
- `tools/list` — Lists available tools
|
|
230
233
|
- `tools/call` — Calls a tool (with ZK-Receipt verification)
|
|
@@ -254,7 +257,7 @@ await bridge.connect();
|
|
|
254
257
|
├─────────────────────────────────────────────────────┤
|
|
255
258
|
│ Layer 4: ZK-Receipt (Integrity Verification) │
|
|
256
259
|
│ SHA-256 ImageID + SHA-512 RISC0-style Seal │
|
|
257
|
-
│ LiopMcpBridge verifies before forwarding to LLM
|
|
260
|
+
│ LiopMcpBridge verifies before forwarding to LLM │
|
|
258
261
|
└─────────────────────────────────────────────────────┘
|
|
259
262
|
```
|
|
260
263
|
|
|
@@ -263,13 +266,21 @@ await bridge.connect();
|
|
|
263
266
|
Built-in patterns with multi-layer verification:
|
|
264
267
|
|
|
265
268
|
```typescript
|
|
266
|
-
import { PII_PATTERNS } from "@nekzus/liop/server";
|
|
269
|
+
import { PII_PATTERNS, PII_PRESETS } from "@nekzus/liop/server";
|
|
267
270
|
|
|
268
271
|
// Available patterns:
|
|
269
272
|
PII_PATTERNS.EMAIL // RFC 5322 compliant, excludes @example.com/@test.com
|
|
270
273
|
PII_PATTERNS.CREDIT_CARD // Visa/MC/Amex + Luhn algorithm validation
|
|
271
274
|
PII_PATTERNS.IP_ADDRESS // IPv4 with octet range validation (excludes localhost)
|
|
272
275
|
PII_PATTERNS.PHONE // International phone formats with digit-length validation
|
|
276
|
+
PII_PATTERNS.SSN // USA Social Security Number rules (blocks 000 segments)
|
|
277
|
+
PII_PATTERNS.IBAN // ISO 7064 Modulo 97-10 algorithm precision via BigInt
|
|
278
|
+
PII_PATTERNS.PASSPORT_MRZ // Strict 44-character TD3 international passport MRZ string
|
|
279
|
+
|
|
280
|
+
// Pre-built Regional Presets ready to drop-in via LiopServer(options):
|
|
281
|
+
PII_PRESETS.GLOBAL_STRICT
|
|
282
|
+
PII_PRESETS.US_COMPLIANT
|
|
283
|
+
PII_PRESETS.EU_GDPR
|
|
273
284
|
```
|
|
274
285
|
|
|
275
286
|
### Forbidden Keys
|
|
@@ -292,7 +303,7 @@ const server = new LiopServer(info, {
|
|
|
292
303
|
The following shows a complete Logic-Injection-on-Origin execution cycle (handled internally by the SDK):
|
|
293
304
|
|
|
294
305
|
```
|
|
295
|
-
1. LLM generates JavaScript analysis code wrapped in
|
|
306
|
+
1. LLM generates JavaScript analysis code wrapped in @LIOP / @END boundaries
|
|
296
307
|
2. LiopServer receives the payload via tools/call (JSON-RPC or direct)
|
|
297
308
|
3. Guardian AST inspects for sandbox escapes (zero-time heuristic analysis)
|
|
298
309
|
4. Code executes inside a V8 isolate with CPU fuel limits (no Node.js globals)
|
|
@@ -379,26 +390,27 @@ await server.connectToMesh();
|
|
|
379
390
|
|
|
380
391
|
This package is continuously tested across multiple platforms and Node.js versions via CI/CD:
|
|
381
392
|
|
|
382
|
-
- **
|
|
393
|
+
- **209+ tests** spanning unit, integration, conformance, and crossnet suites
|
|
383
394
|
- **Multi-OS matrix:** Ubuntu, Windows, macOS
|
|
384
395
|
- **Node.js versions:** 22.x, 24.x
|
|
385
396
|
- **Code quality:** Enforced by [Biome.js](https://biomejs.dev/) (linting + formatting)
|
|
397
|
+
- **Security:** Verified defense-in-depth architecture — see [Security Architecture](https://nekzus-32.mintlify.app/typescript-sdk/security)
|
|
386
398
|
|
|
387
|
-
> To run tests locally or contribute, clone the [repository](https://github.com/Nekzus/
|
|
399
|
+
> To run tests locally or contribute, clone the [repository](https://github.com/Nekzus/LIOP) and follow the [Contributing Guide](https://github.com/Nekzus/LIOP/blob/main/CONTRIBUTING.md).
|
|
388
400
|
|
|
389
401
|
---
|
|
390
402
|
|
|
391
403
|
## Related
|
|
392
404
|
|
|
393
|
-
- [LIOP Documentation](https://
|
|
394
|
-
- [LIOP Specification](https://github.com/Nekzus/
|
|
395
|
-
- [LIOP Manifesto](https://github.com/Nekzus/
|
|
396
|
-
- [Contributing Guide](https://github.com/Nekzus/
|
|
397
|
-
- [Rust Mesh Node](https://github.com/Nekzus/
|
|
398
|
-
- [LIOP CLI](https://github.com/Nekzus/
|
|
405
|
+
- [LIOP Documentation](https://nekzus-32.mintlify.app/) — Full conceptual and API documentation
|
|
406
|
+
- [LIOP Specification](https://github.com/Nekzus/LIOP/blob/main/protocol/SPECIFICATION.md) — Technical specification
|
|
407
|
+
- [LIOP Manifesto](https://github.com/Nekzus/LIOP/blob/main/MANIFESTO.md) — Project philosophy
|
|
408
|
+
- [Contributing Guide](https://github.com/Nekzus/LIOP/blob/main/CONTRIBUTING.md) — How to contribute
|
|
409
|
+
- [Rust Mesh Node](https://github.com/Nekzus/LIOP/tree/main/servers/liop-node) — Native high-performance backend
|
|
410
|
+
- [LIOP CLI](https://github.com/Nekzus/LIOP/tree/main/tools/liop-cli) — Developer diagnostics
|
|
399
411
|
|
|
400
412
|
---
|
|
401
413
|
|
|
402
414
|
## License
|
|
403
415
|
|
|
404
|
-
[MIT](https://github.com/Nekzus/
|
|
416
|
+
[MIT](https://github.com/Nekzus/LIOP/blob/main/LICENSE) © [Nekzus](https://github.com/Nekzus)
|
package/dist/bin/agent.js
CHANGED
|
@@ -2,9 +2,105 @@
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
+
import { multiaddr } from "@multiformats/multiaddr";
|
|
5
6
|
import { LiopMcpRouter } from "../gateway/router.js";
|
|
6
7
|
import { MeshNode } from "../mesh/index.js";
|
|
7
8
|
import { LiopServer } from "../server/index.js";
|
|
9
|
+
import { log } from "../utils/logger.js";
|
|
10
|
+
/**
|
|
11
|
+
* Resolves a full libp2p multiaddr (with PeerID) from a LIOP node's
|
|
12
|
+
* HTTP health endpoint. This enables zero-config bootstrap — users
|
|
13
|
+
* only need to provide a URL, not a cryptographic PeerID.
|
|
14
|
+
*
|
|
15
|
+
* @param url - HTTP URL of a LIOP node's health endpoint (e.g. "http://host:3000")
|
|
16
|
+
* @returns Full multiaddr string with PeerID, or null if resolution fails
|
|
17
|
+
*/
|
|
18
|
+
async function resolveBootstrapFromUrl(url) {
|
|
19
|
+
try {
|
|
20
|
+
const healthUrl = url.endsWith("/health") ? url : `${url}/health`;
|
|
21
|
+
const response = await fetch(healthUrl, {
|
|
22
|
+
headers: { Accept: "application/json" },
|
|
23
|
+
signal: AbortSignal.timeout(10000), // Increased to 10s
|
|
24
|
+
});
|
|
25
|
+
if (!response.ok)
|
|
26
|
+
return null;
|
|
27
|
+
const data = await response.json();
|
|
28
|
+
if (!data.mesh?.multiaddrs?.length || !data.mesh?.peerId)
|
|
29
|
+
return null;
|
|
30
|
+
// Find TCP multiaddr (prefer non-websocket for stability)
|
|
31
|
+
const tcpAddr = data.mesh.multiaddrs.find((a) => a.includes("/tcp/") &&
|
|
32
|
+
!a.includes("/ws") &&
|
|
33
|
+
!a.includes("/ip4/127.0.0.1/"));
|
|
34
|
+
if (!tcpAddr)
|
|
35
|
+
return null;
|
|
36
|
+
// Rewrite internal Docker IP using industrial mapper if available
|
|
37
|
+
let resolved = industrialAddressMapper(tcpAddr);
|
|
38
|
+
if (!resolved || resolved === tcpAddr) {
|
|
39
|
+
const urlHost = new URL(url).hostname;
|
|
40
|
+
resolved = tcpAddr.replace(/\/ip4\/[^/]+/, `/ip4/${urlHost}`);
|
|
41
|
+
}
|
|
42
|
+
if (!resolved)
|
|
43
|
+
return null;
|
|
44
|
+
resolved += resolved.includes("/p2p/") ? "" : `/p2p/${data.mesh.peerId}`;
|
|
45
|
+
return resolved;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Normalizes a bootstrap multiaddr string.
|
|
53
|
+
* If the address contains a Docker bridge IP (172.16-31.x.x) or Loopback (127.0.0.1),
|
|
54
|
+
* rewrites it to the host accessible via LIOP_NEXUS_URL (e.g. WSL2 IP).
|
|
55
|
+
* This is critical when WSL2 mirror-mode networking is broken.
|
|
56
|
+
*/
|
|
57
|
+
function normalizeBootstrap(addr) {
|
|
58
|
+
const trimmed = addr.trim();
|
|
59
|
+
// Remap Docker bridge IPs and ANY external physical IPs to 127.0.0.1
|
|
60
|
+
// because Test-NetConnection confirmed 127.0.0.1 is the only reliable path to Docker ports.
|
|
61
|
+
const dockerIpRegex = /\/ip4\/172\.(1[6-9]|2[0-9]|3[0-1])\.[0-9]{1,3}\.[0-9]{1,3}/;
|
|
62
|
+
const loopbackRegex = /\/ip4\/127\.0\.0\.1/;
|
|
63
|
+
const physicalIpRegex = /\/ip4\/192\.168\.[0-9]{1,3}\.[0-9]{1,3}/;
|
|
64
|
+
if (dockerIpRegex.test(trimmed) ||
|
|
65
|
+
loopbackRegex.test(trimmed) ||
|
|
66
|
+
physicalIpRegex.test(trimmed)) {
|
|
67
|
+
const targetIp = "127.0.0.1";
|
|
68
|
+
const normalized = trimmed
|
|
69
|
+
.replace(dockerIpRegex, `/ip4/${targetIp}`)
|
|
70
|
+
.replace(loopbackRegex, `/ip4/${targetIp}`)
|
|
71
|
+
.replace(physicalIpRegex, `/ip4/${targetIp}`);
|
|
72
|
+
if (normalized !== trimmed) {
|
|
73
|
+
log.info(`[LIOP-Agent] 🔄 Local Routing Hack → Forced 127.0.0.1: ${normalized}`);
|
|
74
|
+
}
|
|
75
|
+
return normalized;
|
|
76
|
+
}
|
|
77
|
+
return trimmed;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* industrialAddressMapper
|
|
81
|
+
*
|
|
82
|
+
* Mapea IPs internas de Docker a puertos industriales mapeados en el Host.
|
|
83
|
+
* Nexus (172.20.0.10) -> 13001
|
|
84
|
+
* Vault (172.20.0.11) -> 13003
|
|
85
|
+
* Bank (172.20.0.12) -> 13004
|
|
86
|
+
* Oracle(172.20.0.13) -> 13005
|
|
87
|
+
*/
|
|
88
|
+
function industrialAddressMapper(addr) {
|
|
89
|
+
if (addr.includes("/ip4/172.20.0.10"))
|
|
90
|
+
return addr.replace(/\/ip4\/172\.20\.0\.10\/tcp\/[0-9]+/, "/ip4/127.0.0.1/tcp/13001");
|
|
91
|
+
if (addr.includes("/ip4/172.20.0.11"))
|
|
92
|
+
return addr.replace(/\/ip4\/172\.20\.0\.11\/tcp\/[0-9]+/, "/ip4/127.0.0.1/tcp/13003");
|
|
93
|
+
if (addr.includes("/ip4/172.20.0.12"))
|
|
94
|
+
return addr.replace(/\/ip4\/172\.20\.0\.12\/tcp\/[0-9]+/, "/ip4/127.0.0.1/tcp/13004");
|
|
95
|
+
if (addr.includes("/ip4/172.20.0.13"))
|
|
96
|
+
return addr.replace(/\/ip4\/172\.20\.0\.13\/tcp\/[0-9]+/, "/ip4/127.0.0.1/tcp/13005");
|
|
97
|
+
// Drop container-internal loopbacks to prevent the Host Agent from dialing itself or conflicting ports
|
|
98
|
+
if (addr.includes("/ip4/127.0.0.1/tcp/4000") ||
|
|
99
|
+
addr.includes("/ip4/127.0.0.1/tcp/3000")) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
return addr;
|
|
103
|
+
}
|
|
8
104
|
/**
|
|
9
105
|
* LIOP Agent (Zero-Config CLI)
|
|
10
106
|
*
|
|
@@ -15,6 +111,8 @@ import { LiopServer } from "../server/index.js";
|
|
|
15
111
|
* No hardcoded tools, PeerIDs, or port mappings.
|
|
16
112
|
*/
|
|
17
113
|
async function main() {
|
|
114
|
+
const buildTime = new Date().toISOString();
|
|
115
|
+
log.info(`[LIOP-Agent] 🚀 Version 1.2.0-alpha.9 | Build: ${buildTime}`);
|
|
18
116
|
const liopDir = path.join(os.homedir(), ".liop");
|
|
19
117
|
const identityPath = path.join(liopDir, "identity.json");
|
|
20
118
|
if (!fs.existsSync(liopDir)) {
|
|
@@ -27,31 +125,41 @@ async function main() {
|
|
|
27
125
|
if (args.length > 0) {
|
|
28
126
|
bootstrapNodes = args.filter((a) => a.startsWith("/"));
|
|
29
127
|
}
|
|
30
|
-
//
|
|
31
|
-
if (bootstrapNodes.length === 0 && process.env.LIOP_BOOTSTRAP) {
|
|
32
|
-
bootstrapNodes.push(process.env.LIOP_BOOTSTRAP.trim());
|
|
33
|
-
}
|
|
34
|
-
// Convenience: Try to read nexus.multiaddr from multiple locations
|
|
128
|
+
// Priority 1: Physical Beacons (Industrial Pattern) - DETERMINISTIC & INSTANT
|
|
35
129
|
if (bootstrapNodes.length === 0) {
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
130
|
+
const searchDirs = [];
|
|
131
|
+
// Priority 1.1: Explicit file from environment variable
|
|
132
|
+
if (process.env.LIOP_BOOTSTRAP_FILE) {
|
|
133
|
+
const filePath = path.resolve(process.env.LIOP_BOOTSTRAP_FILE);
|
|
134
|
+
if (fs.existsSync(filePath)) {
|
|
135
|
+
const addr = fs.readFileSync(filePath, "utf8").trim();
|
|
136
|
+
if (addr)
|
|
137
|
+
bootstrapNodes.push(normalizeBootstrap(addr));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Priority 1.2: Traditional locations (Scan for all *.multiaddr)
|
|
141
|
+
searchDirs.push(process.cwd(), path.join(process.cwd(), "tests/infra/nexus-data"), liopDir, path.join(path
|
|
142
|
+
.dirname(new URL(import.meta.url).pathname)
|
|
143
|
+
.replace(/^\/([A-Z]:)/, "$1"), "../../tests/infra/nexus-data"));
|
|
144
|
+
for (const dir of searchDirs) {
|
|
47
145
|
try {
|
|
48
|
-
if (fs.existsSync(
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
146
|
+
if (fs.existsSync(dir)) {
|
|
147
|
+
const files = fs.readdirSync(dir);
|
|
148
|
+
const multiaddrFiles = files.filter((f) => f.endsWith(".multiaddr"));
|
|
149
|
+
for (const file of multiaddrFiles) {
|
|
150
|
+
const filePath = path.join(dir, file);
|
|
151
|
+
const addr = fs.readFileSync(filePath, "utf8").trim();
|
|
152
|
+
if (addr) {
|
|
153
|
+
const normalized = normalizeBootstrap(addr);
|
|
154
|
+
if (!bootstrapNodes.includes(normalized)) {
|
|
155
|
+
bootstrapNodes.push(normalized);
|
|
156
|
+
log.info(`[LIOP-Agent] ✅ Loaded beacon: ${file} from ${dir}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
54
159
|
}
|
|
160
|
+
// If we found any beacons in this directory, we consider discovery successful for this layer
|
|
161
|
+
if (bootstrapNodes.length > 0)
|
|
162
|
+
break;
|
|
55
163
|
}
|
|
56
164
|
}
|
|
57
165
|
catch (_e) {
|
|
@@ -59,78 +167,141 @@ async function main() {
|
|
|
59
167
|
}
|
|
60
168
|
}
|
|
61
169
|
}
|
|
170
|
+
// Priority 2: Auto-Discovery via NEXUS URL (Aggressive Parallel Discovery)
|
|
171
|
+
if (process.env.LIOP_NEXUS_URL) {
|
|
172
|
+
const nexusUrl = process.env.LIOP_NEXUS_URL;
|
|
173
|
+
log.info(`[LIOP-Agent] 🌐 Running parallel discovery from: ${nexusUrl} (Sources Found: ${bootstrapNodes.length})`);
|
|
174
|
+
const resolved = await resolveBootstrapFromUrl(nexusUrl);
|
|
175
|
+
if (resolved) {
|
|
176
|
+
const normalized = normalizeBootstrap(resolved);
|
|
177
|
+
if (!bootstrapNodes.includes(normalized)) {
|
|
178
|
+
bootstrapNodes.push(normalized);
|
|
179
|
+
log.info(`[LIOP-Agent] ✅ Added bootstrap from URL discovery: ${normalized}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Priority 3: Environment variable (direct multiaddr)
|
|
184
|
+
if (bootstrapNodes.length === 0 && process.env.LIOP_BOOTSTRAP) {
|
|
185
|
+
bootstrapNodes.push(process.env.LIOP_BOOTSTRAP.trim());
|
|
186
|
+
}
|
|
187
|
+
// Final fallback: local Nexus bootstrap for demo environments.
|
|
188
|
+
// Avoid injecting stale static peer IDs when discovery already found valid peers.
|
|
189
|
+
if (bootstrapNodes.length === 0) {
|
|
190
|
+
bootstrapNodes.push("/ip4/127.0.0.1/tcp/13001/p2p/12D3KooWD8FUFdnLQzzLFNdicsaTknM5cpD7os9sK9NWVSVABJMD");
|
|
191
|
+
}
|
|
192
|
+
// Sanitize/validate all candidate multiaddrs so malformed PeerIDs don't crash startup.
|
|
193
|
+
bootstrapNodes = bootstrapNodes.filter((addr) => {
|
|
194
|
+
try {
|
|
195
|
+
multiaddr(addr);
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
log.warn(`[LIOP-Agent] Ignoring invalid bootstrap multiaddr: ${addr}`);
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
});
|
|
62
203
|
// If no bootstrap nodes found, the agent operates in standalone mode.
|
|
63
204
|
// It will only serve local tools until peers are discovered.
|
|
64
205
|
if (bootstrapNodes.length === 0) {
|
|
65
|
-
|
|
66
|
-
|
|
206
|
+
log.info("[LIOP-Agent] No bootstrap nodes configured. Operating in standalone mode.");
|
|
207
|
+
log.info("[LIOP-Agent] Pass a multiaddr as argument or create 'nexus.multiaddr' file.");
|
|
67
208
|
}
|
|
68
209
|
// Initialize local server node (lightweight, no tools registered locally)
|
|
69
210
|
const liopServer = new LiopServer({
|
|
70
211
|
name: "@nekzus/liop-agent",
|
|
71
212
|
version: "1.0.0",
|
|
72
213
|
});
|
|
214
|
+
// Enable Zero-Shot Autonomy (Industrial Prompt Injection)
|
|
215
|
+
liopServer.enableZeroShotAutonomy();
|
|
73
216
|
// 2. Mesh Node Configuration
|
|
74
217
|
const meshNode = new MeshNode({
|
|
75
218
|
identityPath: identityPath,
|
|
76
219
|
bootstrapNodes: bootstrapNodes,
|
|
220
|
+
addressMapper: industrialAddressMapper,
|
|
77
221
|
});
|
|
78
222
|
// Start P2P Mesh
|
|
79
223
|
await meshNode.start();
|
|
80
224
|
// 3. Initialize the Dynamic Router
|
|
81
225
|
// No hardcoded tools — all discovery happens via liop:manifest protocol
|
|
82
226
|
const router = new LiopMcpRouter(liopServer, meshNode);
|
|
83
|
-
// Proactive Notification to Claude Desktop when tools are discovered dynamically
|
|
227
|
+
// Proactive Notification to Claude Desktop when tools/resources are discovered dynamically
|
|
84
228
|
router.onToolsChanged = () => {
|
|
85
229
|
process.stdout.write(`{"jsonrpc":"2.0","method":"notifications/tools/list_changed"}\n`);
|
|
230
|
+
process.stdout.write(`{"jsonrpc":"2.0","method":"notifications/resources/list_changed"}\n`);
|
|
86
231
|
};
|
|
87
|
-
// Initial warming period (2s) then
|
|
88
|
-
//
|
|
232
|
+
// Initial warming period (2s) then Adaptive Background Discovery
|
|
233
|
+
// Polls DHT for new nodes and triggers onToolsChanged when topology shifts.
|
|
234
|
+
// Uses exponential backoff to reduce polling load on stable meshes.
|
|
89
235
|
setTimeout(() => {
|
|
90
236
|
// biome-ignore lint/suspicious/noExplicitAny: access internal for telemetry
|
|
91
237
|
const rtSize = meshNode.getRoutingTableSize?.() || 0;
|
|
92
|
-
|
|
238
|
+
log.info(`[LIOP-Agent] Warm-up complete. Routing Table size: ${rtSize}`);
|
|
93
239
|
router.refreshManifestCache(true).catch(() => { });
|
|
94
240
|
}, 2000);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
241
|
+
const POLL_BASE_MS = 10_000;
|
|
242
|
+
const POLL_MAX_MS = 120_000;
|
|
243
|
+
let pollIntervalMs = POLL_BASE_MS;
|
|
244
|
+
const scheduleAdaptivePoll = () => {
|
|
245
|
+
setTimeout(async () => {
|
|
246
|
+
const prevSize = router.getCacheSize();
|
|
247
|
+
await router.refreshManifestCache(true).catch(() => { });
|
|
248
|
+
const newSize = router.getCacheSize();
|
|
249
|
+
if (newSize !== prevSize) {
|
|
250
|
+
// Topology changed — reset to aggressive polling
|
|
251
|
+
pollIntervalMs = POLL_BASE_MS;
|
|
252
|
+
log.info(`[LIOP-Agent] Topology change detected (${prevSize} → ${newSize}). Resetting poll to ${POLL_BASE_MS / 1000}s.`);
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
// Stable — relax polling interval (factor 1.5)
|
|
256
|
+
pollIntervalMs = Math.min(Math.round(pollIntervalMs * 1.5), POLL_MAX_MS);
|
|
257
|
+
}
|
|
258
|
+
scheduleAdaptivePoll();
|
|
259
|
+
}, pollIntervalMs);
|
|
260
|
+
};
|
|
261
|
+
scheduleAdaptivePoll();
|
|
262
|
+
// 4. STDIO Transport — Buffered Line Reader
|
|
263
|
+
// Uses readline to guarantee complete JSON-RPC messages before parsing.
|
|
264
|
+
// Raw stdin.on("data") can fragment large payloads across multiple chunks.
|
|
265
|
+
const readline = await import("node:readline");
|
|
266
|
+
const rl = readline.createInterface({
|
|
267
|
+
input: process.stdin,
|
|
268
|
+
terminal: false,
|
|
269
|
+
});
|
|
99
270
|
process.stdout.on("error", (err) => {
|
|
100
271
|
if (err.code === "EPIPE") {
|
|
101
272
|
process.exit(0); // Graceful exit when Claude Desktop disconnects
|
|
102
273
|
}
|
|
103
274
|
});
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
if (!
|
|
275
|
+
rl.on("line", async (line) => {
|
|
276
|
+
const trimmed = line.trim();
|
|
277
|
+
if (!trimmed)
|
|
107
278
|
return;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
if (response) {
|
|
115
|
-
process.stdout.write(`${JSON.stringify(response)}\n`);
|
|
116
|
-
}
|
|
279
|
+
try {
|
|
280
|
+
const request = JSON.parse(trimmed);
|
|
281
|
+
if (request.method) {
|
|
282
|
+
const response = await router.dispatch(request);
|
|
283
|
+
if (response) {
|
|
284
|
+
process.stdout.write(`${JSON.stringify(response)}\n`);
|
|
117
285
|
}
|
|
118
286
|
}
|
|
119
|
-
catch (_err) {
|
|
120
|
-
// Silent catch for binary noise
|
|
121
|
-
}
|
|
122
287
|
}
|
|
288
|
+
catch (_err) {
|
|
289
|
+
// Silent catch for binary noise or malformed lines
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
rl.on("close", () => {
|
|
293
|
+
process.exit(0);
|
|
123
294
|
});
|
|
124
295
|
// Status directed only to stderr
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
296
|
+
log.info(`[LIOP-Agent] Guarding Claude Desktop via STDIO.`);
|
|
297
|
+
log.info(`[LIOP-Agent] P2P Mesh: Joined (${bootstrapNodes.length} bootstraps)`);
|
|
298
|
+
log.info("[LIOP-Agent] Tool discovery: Dynamic via /liop/manifest/1.0.0");
|
|
128
299
|
process.on("SIGINT", async () => {
|
|
129
300
|
await meshNode.stop();
|
|
130
301
|
process.exit(0);
|
|
131
302
|
});
|
|
132
303
|
}
|
|
133
304
|
main().catch((err) => {
|
|
134
|
-
|
|
305
|
+
log.error(`[LIOP-Agent] Fatal Error: ${err.message}`);
|
|
135
306
|
process.exit(1);
|
|
136
307
|
});
|
package/dist/bridge/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { LiopServer } from "../server/index.js";
|
|
3
|
+
import { log } from "../utils/logger.js";
|
|
3
4
|
/**
|
|
4
5
|
* LIOP MCP Bridge
|
|
5
6
|
* A bi-directional bridge that allows legacy MCP servers to join the LIOP mesh,
|
|
@@ -14,11 +15,11 @@ export class LiopMcpBridge {
|
|
|
14
15
|
// Determine mode: Exposing LIOP to MCP (Claude) or Wrapping MCP to LIOP (Mesh)
|
|
15
16
|
if (source instanceof LiopServer) {
|
|
16
17
|
this.liopServer = source;
|
|
17
|
-
|
|
18
|
+
log.info("[LIOP-Bridge] Mode: EXPOSE (LIOP -> MCP Stdio)");
|
|
18
19
|
}
|
|
19
20
|
else if (source instanceof McpServer) {
|
|
20
21
|
this.legacyMcpServer = source;
|
|
21
|
-
|
|
22
|
+
log.info("[LIOP-Bridge] Mode: WRAP (Legacy MCP -> LIOP Mesh)");
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
/**
|
|
@@ -47,7 +48,7 @@ export class LiopMcpBridge {
|
|
|
47
48
|
return null;
|
|
48
49
|
if (method === "initialize") {
|
|
49
50
|
return this.successResponse(id, {
|
|
50
|
-
protocolVersion: "2025-
|
|
51
|
+
protocolVersion: "2025-11-25",
|
|
51
52
|
capabilities: {
|
|
52
53
|
prompts: {},
|
|
53
54
|
resources: {},
|
|
@@ -167,7 +168,7 @@ export class LiopMcpBridge {
|
|
|
167
168
|
return true;
|
|
168
169
|
}
|
|
169
170
|
catch (e) {
|
|
170
|
-
|
|
171
|
+
log.info("[LIOP-Bridge] ZK-Verifier Failure:", e);
|
|
171
172
|
return false;
|
|
172
173
|
}
|
|
173
174
|
}
|
|
@@ -221,7 +222,7 @@ export class LiopMcpBridge {
|
|
|
221
222
|
terminal: false,
|
|
222
223
|
});
|
|
223
224
|
const shutdown = async () => {
|
|
224
|
-
|
|
225
|
+
log.info("[LIOP-Bridge] Disconnecting session...");
|
|
225
226
|
if (this.liopServer)
|
|
226
227
|
await this.liopServer.close();
|
|
227
228
|
process.exit(0);
|
|
@@ -240,7 +241,7 @@ export class LiopMcpBridge {
|
|
|
240
241
|
}
|
|
241
242
|
}
|
|
242
243
|
catch (e) {
|
|
243
|
-
|
|
244
|
+
log.error(`[LIOP-Bridge] Error: ${e.message}`);
|
|
244
245
|
}
|
|
245
246
|
});
|
|
246
247
|
}
|