@nekzus/liop 1.2.0-alpha.10
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/LICENSE +21 -0
- package/README.md +413 -0
- package/dist/bin/agent.d.ts +2 -0
- package/dist/bin/agent.js +307 -0
- package/dist/bridge/index.d.ts +37 -0
- package/dist/bridge/index.js +249 -0
- package/dist/bridge/stream.d.ts +62 -0
- package/dist/bridge/stream.js +202 -0
- package/dist/client/index.d.ts +60 -0
- package/dist/client/index.js +275 -0
- 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 +29 -0
- package/dist/crypto/verifier.js +96 -0
- 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/gateway/hybrid.d.ts +23 -0
- package/dist/gateway/hybrid.js +199 -0
- package/dist/gateway/router.d.ts +69 -0
- package/dist/gateway/router.js +1036 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/mesh/index.d.ts +1 -0
- package/dist/mesh/index.js +1 -0
- package/dist/mesh/node.d.ts +129 -0
- package/dist/mesh/node.js +853 -0
- package/dist/prompts/adapters.d.ts +16 -0
- package/dist/prompts/adapters.js +55 -0
- package/dist/protocol/liop_core.proto +44 -0
- package/dist/rpc/client.d.ts +22 -0
- package/dist/rpc/client.js +40 -0
- package/dist/rpc/codec/lpm.d.ts +20 -0
- package/dist/rpc/codec/lpm.js +36 -0
- package/dist/rpc/crypto/aes.d.ts +22 -0
- package/dist/rpc/crypto/aes.js +47 -0
- package/dist/rpc/crypto/kyber.d.ts +27 -0
- package/dist/rpc/crypto/kyber.js +70 -0
- package/dist/rpc/proto.d.ts +2 -0
- package/dist/rpc/proto.js +33 -0
- package/dist/rpc/server.d.ts +13 -0
- package/dist/rpc/server.js +50 -0
- package/dist/rpc/tls.d.ts +26 -0
- package/dist/rpc/tls.js +54 -0
- package/dist/rpc/types.d.ts +28 -0
- package/dist/rpc/types.js +5 -0
- package/dist/sandbox/guardian.d.ts +18 -0
- package/dist/sandbox/guardian.js +35 -0
- package/dist/sandbox/wasi.d.ts +36 -0
- package/dist/sandbox/wasi.js +179 -0
- package/dist/security/guardian.d.ts +22 -0
- package/dist/security/guardian.js +52 -0
- package/dist/security/zk.d.ts +37 -0
- package/dist/security/zk.js +66 -0
- package/dist/server/index.d.ts +184 -0
- package/dist/server/index.js +933 -0
- package/dist/server/pii.d.ts +40 -0
- package/dist/server/pii.js +266 -0
- package/dist/types.d.ts +145 -0
- package/dist/types.js +26 -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 +17 -0
- package/dist/workers/logic-execution.js +121 -0
- package/dist/workers/zk-verifier.d.ts +20 -0
- package/dist/workers/zk-verifier.js +84 -0
- package/package.json +147 -0
|
@@ -0,0 +1,933 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
+
import * as grpc from "@grpc/grpc-js";
|
|
7
|
+
import { FixedQueue, Piscina } from "piscina";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
10
|
+
import { MeshNode } from "../mesh/node.js";
|
|
11
|
+
import { LiopRpcServer } from "../rpc/server.js";
|
|
12
|
+
import { log } from "../utils/logger.js";
|
|
13
|
+
import { PII_PATTERNS, PII_PRESETS, PiiScanner } from "./pii.js";
|
|
14
|
+
export { PII_PATTERNS, PII_PRESETS, PiiScanner };
|
|
15
|
+
/**
|
|
16
|
+
* When enabled, `payload` tools that are not LIOP v1 envelopes are passed through to the
|
|
17
|
+
* registered handler unchanged (no worker extraction). Default off for strict protocol tests.
|
|
18
|
+
*/
|
|
19
|
+
function respectPlainToolPayload() {
|
|
20
|
+
const v = process.env.LIOP_RESPECT_PLAIN_TOOL_PAYLOAD?.toLowerCase().trim();
|
|
21
|
+
return v === "1" || v === "true" || v === "yes";
|
|
22
|
+
}
|
|
23
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
export class LiopServer {
|
|
25
|
+
serverInfo;
|
|
26
|
+
config;
|
|
27
|
+
logicCache = new Map();
|
|
28
|
+
connectionStats = new Map();
|
|
29
|
+
CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
30
|
+
THROTTLE_THRESHOLD = 5;
|
|
31
|
+
THROTTLE_COOLDOWN_MS = 60 * 1000; // 60 seconds
|
|
32
|
+
tools = new Map();
|
|
33
|
+
resources = new Map();
|
|
34
|
+
prompts = new Map();
|
|
35
|
+
activeSchema = null;
|
|
36
|
+
sandboxRecords = [];
|
|
37
|
+
piiScanner;
|
|
38
|
+
workerPool;
|
|
39
|
+
meshNode = null;
|
|
40
|
+
rpcServer = null;
|
|
41
|
+
boundPort = null;
|
|
42
|
+
sessions = new Map();
|
|
43
|
+
static LIOP_LOGIC_REGEX = /\s*LIOP_MAGIC:0x00FF\s*\n?\s*MANIFEST:(?<manifest>\{[\s\S]*?\})\s*\n?\s*---BEGIN_LOGIC---\n?(?<logic>[\s\S]*?)\n?---END_LOGIC---/m;
|
|
44
|
+
// Compact envelope: @LIOP{target,name}\n<code>\n@END
|
|
45
|
+
static LIOP_COMPACT_REGEX = /@LIOP\{(?<target>[^,}]+)(?:,(?<name>[^}]*))?\}\n(?<logic>[\s\S]*?)\n@END/m;
|
|
46
|
+
extractLogic(payload) {
|
|
47
|
+
const match = payload.match(LiopServer.LIOP_LOGIC_REGEX);
|
|
48
|
+
if (match?.groups?.logic)
|
|
49
|
+
return match.groups.logic.trim();
|
|
50
|
+
// Compact envelope fallback — same execution, fewer tokens
|
|
51
|
+
const compact = payload.match(LiopServer.LIOP_COMPACT_REGEX);
|
|
52
|
+
return compact?.groups?.logic ? compact.groups.logic.trim() : null;
|
|
53
|
+
}
|
|
54
|
+
parseUnknownJson(input) {
|
|
55
|
+
if (typeof input !== "string")
|
|
56
|
+
return input;
|
|
57
|
+
const trimmed = input.trim();
|
|
58
|
+
if ((trimmed.startsWith("{") && trimmed.endsWith("}")) ||
|
|
59
|
+
(trimmed.startsWith("[") && trimmed.endsWith("]"))) {
|
|
60
|
+
try {
|
|
61
|
+
return JSON.parse(trimmed);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return input;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return input;
|
|
68
|
+
}
|
|
69
|
+
runPreflightPolicy(_toolName, logic, policy) {
|
|
70
|
+
if (!policy)
|
|
71
|
+
return null;
|
|
72
|
+
const compact = logic.replace(/\s+/g, " ");
|
|
73
|
+
if (policy.enforceAggregationFirst) {
|
|
74
|
+
const rowExtractionPatterns = [
|
|
75
|
+
/return\s+env\.records\b/i,
|
|
76
|
+
/return\s*\{[\s\S]*\b(accounts|patients|rows|records)\s*:\s*env\.records/i,
|
|
77
|
+
];
|
|
78
|
+
if (rowExtractionPatterns.some((p) => p.test(compact))) {
|
|
79
|
+
return "Preflight policy rejected: potential row-level export pattern detected.";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (policy.preflightDenyPatterns?.some((p) => p.test(compact))) {
|
|
83
|
+
return "Preflight policy rejected: custom deny pattern matched.";
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
validateOutputPolicy(toolName, output, policy) {
|
|
88
|
+
if (!policy)
|
|
89
|
+
return null;
|
|
90
|
+
const parsed = this.parseUnknownJson(output);
|
|
91
|
+
if (policy.outputSchema) {
|
|
92
|
+
const schemaResult = policy.outputSchema.safeParse(parsed);
|
|
93
|
+
if (!schemaResult.success) {
|
|
94
|
+
// Include a truncated preview of the rejected value so the LLM can self-correct
|
|
95
|
+
const preview = typeof parsed === "string"
|
|
96
|
+
? parsed.slice(0, 200)
|
|
97
|
+
: JSON.stringify(parsed).slice(0, 200);
|
|
98
|
+
return `[LIOP] Output schema violation for ${toolName}: ${schemaResult.error.issues
|
|
99
|
+
.map((i) => `${i.path.join(".") || "<root>"} ${i.message}`)
|
|
100
|
+
.join("; ")}. Rejected value: ${preview}. HINT: Use 'env.records' to access the dataset inside your logic.`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (policy.enforceAggregationFirst &&
|
|
104
|
+
this.violatesAggregationFirstPolicy(this.unwrapForAggregationPolicyScan(parsed))) {
|
|
105
|
+
return "Aggregation-First Policy Violation: row-level export blocked. HINT: Use .reduce() to produce a flat {key:value} object. Do NOT use .map() to create arrays of objects.";
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Proxied tools stringify a full MCP CallToolResult (`{ content: [...] }`).
|
|
111
|
+
* Aggregation-first heuristics must scan the inner business JSON, not the MCP envelope
|
|
112
|
+
* (otherwise `content` looks like a tabular array of objects and everything blocks).
|
|
113
|
+
*/
|
|
114
|
+
unwrapForAggregationPolicyScan(input) {
|
|
115
|
+
if (typeof input === "string") {
|
|
116
|
+
const trimmed = input.trim();
|
|
117
|
+
if ((trimmed.startsWith("{") && trimmed.endsWith("}")) ||
|
|
118
|
+
(trimmed.startsWith("[") && trimmed.endsWith("]"))) {
|
|
119
|
+
try {
|
|
120
|
+
return this.unwrapForAggregationPolicyScan(JSON.parse(trimmed));
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return input;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return input;
|
|
127
|
+
}
|
|
128
|
+
if (!input || typeof input !== "object") {
|
|
129
|
+
return input;
|
|
130
|
+
}
|
|
131
|
+
const rec = input;
|
|
132
|
+
if (!Array.isArray(rec.content) || rec.content.length === 0) {
|
|
133
|
+
return input;
|
|
134
|
+
}
|
|
135
|
+
const texts = [];
|
|
136
|
+
for (const part of rec.content) {
|
|
137
|
+
if (part && typeof part === "object" && "text" in part) {
|
|
138
|
+
const t = part.text;
|
|
139
|
+
if (typeof t === "string") {
|
|
140
|
+
texts.push(t);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (texts.length === 0) {
|
|
145
|
+
return input;
|
|
146
|
+
}
|
|
147
|
+
const joined = texts.length === 1 ? texts[0] : texts.join("\n");
|
|
148
|
+
return this.unwrapForAggregationPolicyScan(joined);
|
|
149
|
+
}
|
|
150
|
+
violatesAggregationFirstPolicy(input) {
|
|
151
|
+
if (typeof input === "string") {
|
|
152
|
+
const trimmed = input.trim();
|
|
153
|
+
if ((trimmed.startsWith("{") && trimmed.endsWith("}")) ||
|
|
154
|
+
(trimmed.startsWith("[") && trimmed.endsWith("]"))) {
|
|
155
|
+
try {
|
|
156
|
+
return this.violatesAggregationFirstPolicy(JSON.parse(trimmed));
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
if (Array.isArray(input)) {
|
|
165
|
+
if (input.length > 0 && input.every((item) => typeof item === "object")) {
|
|
166
|
+
// Treat tabular row export as non-aggregated leakage risk.
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
return input.some((item) => this.violatesAggregationFirstPolicy(item));
|
|
170
|
+
}
|
|
171
|
+
if (input && typeof input === "object") {
|
|
172
|
+
return Object.values(input).some((value) => this.violatesAggregationFirstPolicy(value));
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
constructor(serverInfo, config) {
|
|
177
|
+
this.serverInfo = serverInfo;
|
|
178
|
+
this.config = config;
|
|
179
|
+
this.piiScanner = new PiiScanner(this.config?.security?.piiPatterns ?? PII_PRESETS.GLOBAL_STRICT, this.config?.security?.forbiddenKeys ?? [
|
|
180
|
+
"id",
|
|
181
|
+
"name",
|
|
182
|
+
"fullName",
|
|
183
|
+
"firstName",
|
|
184
|
+
"lastName",
|
|
185
|
+
"address",
|
|
186
|
+
"street",
|
|
187
|
+
"city",
|
|
188
|
+
"postalCode",
|
|
189
|
+
"zipCode",
|
|
190
|
+
"phone",
|
|
191
|
+
"email",
|
|
192
|
+
"ssn",
|
|
193
|
+
"accountHolder",
|
|
194
|
+
"accountNumber",
|
|
195
|
+
"account_number",
|
|
196
|
+
"password",
|
|
197
|
+
"token",
|
|
198
|
+
"secret",
|
|
199
|
+
"privateKey",
|
|
200
|
+
]);
|
|
201
|
+
// Initialize Zero-Blocking Worker Pool for Heavy Cryptography & Sandboxing
|
|
202
|
+
const isTS = import.meta.url.endsWith(".ts");
|
|
203
|
+
const workerExt = isTS ? ".ts" : ".js";
|
|
204
|
+
let execArgv = [];
|
|
205
|
+
if (isTS) {
|
|
206
|
+
try {
|
|
207
|
+
const req = createRequire(import.meta.url);
|
|
208
|
+
const tsxPkg = req.resolve("tsx/package.json");
|
|
209
|
+
const absoluteTsx = pathToFileURL(path.join(path.dirname(tsxPkg), "dist", "loader.mjs")).href;
|
|
210
|
+
execArgv = ["--import", absoluteTsx];
|
|
211
|
+
}
|
|
212
|
+
catch (_e) {
|
|
213
|
+
execArgv = ["--import", "tsx"];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const isTest = process.env.NODE_ENV === "test" || process.env.VITEST;
|
|
217
|
+
// Sync capabilities to serverInfo for MCP Handshakes
|
|
218
|
+
if (this.config?.capabilities && !this.serverInfo.capabilities) {
|
|
219
|
+
this.serverInfo.capabilities = this.config.capabilities;
|
|
220
|
+
}
|
|
221
|
+
this.workerPool = new Piscina({
|
|
222
|
+
filename: path.resolve(__dirname, `../workers/logic-execution${workerExt}`),
|
|
223
|
+
minThreads: this.config?.workerPool?.minThreads ?? (isTest ? 0 : 2),
|
|
224
|
+
maxThreads: this.config?.workerPool?.maxThreads ?? (isTest ? 1 : 8),
|
|
225
|
+
idleTimeout: this.config?.workerPool?.idleTimeout ?? (isTest ? 500 : 5000),
|
|
226
|
+
maxQueue: "auto",
|
|
227
|
+
taskQueue: new FixedQueue(),
|
|
228
|
+
execArgv,
|
|
229
|
+
});
|
|
230
|
+
// [Token Economy] Auto-register LIOP protocol spec as a single Resource.
|
|
231
|
+
// This centralizes the envelope documentation that was previously
|
|
232
|
+
// duplicated in every tool description, reducing token overhead.
|
|
233
|
+
this.resource("LIOP Envelope Specification", "liop://protocol/envelope-spec", "Complete Logic-on-Origin envelope format, execution rules, and security constraints", "text/plain", () => Promise.resolve(this.buildEnvelopeSpec()));
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Builds the centralized LIOP envelope specification document.
|
|
237
|
+
* Served as a single Resource (liop://protocol/envelope-spec) instead
|
|
238
|
+
* of being duplicated across every tool description.
|
|
239
|
+
*/
|
|
240
|
+
buildEnvelopeSpec() {
|
|
241
|
+
const lines = [
|
|
242
|
+
"LIOP v1 Envelope Specification",
|
|
243
|
+
"================================",
|
|
244
|
+
"",
|
|
245
|
+
"FORMATS (both accepted):",
|
|
246
|
+
"",
|
|
247
|
+
"Compact:",
|
|
248
|
+
" @LIOP{wasi_v1,TaskName}",
|
|
249
|
+
" <JavaScript code>",
|
|
250
|
+
" @END",
|
|
251
|
+
"",
|
|
252
|
+
"Standard:",
|
|
253
|
+
" LIOP_MAGIC:0x00FF",
|
|
254
|
+
' MANIFEST:{"target":"wasi_v1","name":"TaskName","integrity_checks":true}',
|
|
255
|
+
" ---BEGIN_LOGIC---",
|
|
256
|
+
" <JavaScript code>",
|
|
257
|
+
" ---END_LOGIC---",
|
|
258
|
+
"",
|
|
259
|
+
"RUNTIME ENVIRONMENT:",
|
|
260
|
+
"- env.records: Array of data objects from the origin",
|
|
261
|
+
"- Must use 'return' to output results",
|
|
262
|
+
"- Zero-Trust WASI Sandbox (Node.js Worker Pool)",
|
|
263
|
+
"- Return aggregated objects, NOT raw row-level arrays",
|
|
264
|
+
"",
|
|
265
|
+
"SECURITY CONSTRAINTS:",
|
|
266
|
+
"- PII Egress Shield blocks raw identifiers in output",
|
|
267
|
+
"- Aggregation-First policy: prefer counts, averages, summaries",
|
|
268
|
+
"- AST Guardian: static analysis before execution",
|
|
269
|
+
];
|
|
270
|
+
if (this.config?.security?.forbiddenKeys?.length) {
|
|
271
|
+
lines.push(`- Restricted fields: ${this.config.security.forbiddenKeys.join(", ")}`);
|
|
272
|
+
}
|
|
273
|
+
lines.push("", "OPTIONAL PARAMETERS:", "- __liop_bypass_ast_cache: boolean (force AST re-evaluation)");
|
|
274
|
+
return lines.join("\n");
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Extracts a compact, human-readable field summary from a JSON Schema.
|
|
278
|
+
*
|
|
279
|
+
* Walks the schema structure to find actual data property names and types,
|
|
280
|
+
* rather than returning top-level schema metadata keys (type, items, etc.).
|
|
281
|
+
*
|
|
282
|
+
* Example output for a banking schema:
|
|
283
|
+
* "Array of {id(string), accountHolder(string), balance(number), transactions(array of {date(string), amount(number)})}"
|
|
284
|
+
*/
|
|
285
|
+
extractSchemaFieldSummary(schema, depth = 0) {
|
|
286
|
+
// Prevent excessive recursion in deeply nested schemas
|
|
287
|
+
if (depth > 3)
|
|
288
|
+
return "{...}";
|
|
289
|
+
const schemaType = schema.type;
|
|
290
|
+
const properties = schema.properties;
|
|
291
|
+
const items = schema.items;
|
|
292
|
+
// Object with properties → list field names with their types
|
|
293
|
+
if (properties) {
|
|
294
|
+
const fields = Object.entries(properties).map(([key, prop]) => {
|
|
295
|
+
const propType = prop.type;
|
|
296
|
+
if (propType === "array" && prop.items) {
|
|
297
|
+
const nested = this.extractSchemaFieldSummary(prop.items, depth + 1);
|
|
298
|
+
return `${key}(array of ${nested})`;
|
|
299
|
+
}
|
|
300
|
+
if (propType === "object" && prop.properties) {
|
|
301
|
+
const nested = this.extractSchemaFieldSummary(prop, depth + 1);
|
|
302
|
+
return `${key}(${nested})`;
|
|
303
|
+
}
|
|
304
|
+
return `${key}(${propType || "unknown"})`;
|
|
305
|
+
});
|
|
306
|
+
return `{${fields.join(", ")}}`;
|
|
307
|
+
}
|
|
308
|
+
// Array type → describe the items structure
|
|
309
|
+
if (schemaType === "array" && items) {
|
|
310
|
+
const itemsSummary = this.extractSchemaFieldSummary(items, depth + 1);
|
|
311
|
+
return `Array of ${itemsSummary}`;
|
|
312
|
+
}
|
|
313
|
+
// Simple type or unknown structure → fallback to key listing
|
|
314
|
+
if (schemaType)
|
|
315
|
+
return schemaType;
|
|
316
|
+
return Object.keys(schema).join(", ");
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Convenience alias for connectToMesh(), matching official documentation.
|
|
320
|
+
*/
|
|
321
|
+
async connect(options = {}) {
|
|
322
|
+
return this.connectToMesh(options);
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Register a new Tool
|
|
326
|
+
*/
|
|
327
|
+
tool(name, description, shape, handler, policy) {
|
|
328
|
+
if (this.tools.has(name)) {
|
|
329
|
+
throw new Error(`Tool already registered: ${name}`);
|
|
330
|
+
}
|
|
331
|
+
const schema = z.object(shape);
|
|
332
|
+
const generatedSchema = zodToJsonSchema(schema);
|
|
333
|
+
let finalDescription = description;
|
|
334
|
+
let finalHandler = handler;
|
|
335
|
+
// LIOP Zero-Shot Autonomy Middleware: Detect Logic-on-Origin tools
|
|
336
|
+
if (shape.payload && shape.payload instanceof z.ZodString) {
|
|
337
|
+
const blockedKeys = this.config?.security?.forbiddenKeys || [];
|
|
338
|
+
// [Token Economy] Centralized description: reference the protocol spec
|
|
339
|
+
// Resource instead of duplicating the full envelope format per tool.
|
|
340
|
+
// Same information, delivered once via liop://protocol/envelope-spec.
|
|
341
|
+
finalDescription +=
|
|
342
|
+
"\n\nPayload: LIOP v1 envelope (WASI sandbox)." +
|
|
343
|
+
" Format: @LIOP{wasi_v1,TaskName}\\n<JS code>\\n@END" +
|
|
344
|
+
" | Access data: env.records. Return aggregated object." +
|
|
345
|
+
" | Full spec: resource liop://protocol/envelope-spec";
|
|
346
|
+
if (blockedKeys.length > 0) {
|
|
347
|
+
finalDescription += `\nRestricted fields: ${blockedKeys.join(", ")}.`;
|
|
348
|
+
}
|
|
349
|
+
if (this.activeSchema) {
|
|
350
|
+
const schemaDigest = this.extractSchemaFieldSummary(this.activeSchema);
|
|
351
|
+
finalDescription += `\nData structure: ${schemaDigest}. Full schema: resource liop://schema/global`;
|
|
352
|
+
}
|
|
353
|
+
finalHandler = async (args, _extra) => {
|
|
354
|
+
const clientId = "global_connection"; // Simplify for now, treating the instance as one connection
|
|
355
|
+
const now = Date.now();
|
|
356
|
+
const stats = this.connectionStats.get(clientId) || {
|
|
357
|
+
failures: 0,
|
|
358
|
+
lastAttempt: 0,
|
|
359
|
+
};
|
|
360
|
+
if (stats.failures >= this.THROTTLE_THRESHOLD &&
|
|
361
|
+
now - stats.lastAttempt < this.THROTTLE_COOLDOWN_MS) {
|
|
362
|
+
return {
|
|
363
|
+
content: [
|
|
364
|
+
{
|
|
365
|
+
type: "text",
|
|
366
|
+
text: "LIOP_THROTTLED: Too many violations. Cooling down for 60 seconds.",
|
|
367
|
+
},
|
|
368
|
+
],
|
|
369
|
+
isError: true,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
const payloadValue = args
|
|
373
|
+
.payload;
|
|
374
|
+
const bypassCache = args.__liop_bypass_ast_cache === true;
|
|
375
|
+
const payloadHash = crypto
|
|
376
|
+
.createHash("sha256")
|
|
377
|
+
.update(payloadValue)
|
|
378
|
+
.digest("hex");
|
|
379
|
+
const logic = this.extractLogic(payloadValue);
|
|
380
|
+
const cached = this.logicCache.get(payloadHash);
|
|
381
|
+
if (!bypassCache &&
|
|
382
|
+
cached &&
|
|
383
|
+
now - cached.timestamp < this.CACHE_TTL_MS) {
|
|
384
|
+
// Hash verified. Skips boundaries check (already validated!). Extract logic directly.
|
|
385
|
+
if (logic) {
|
|
386
|
+
args.payload = logic;
|
|
387
|
+
// DELEGATE TO WORKER POOL: Parallel PQC & Sandboxing
|
|
388
|
+
const preflightReason = this.runPreflightPolicy(name, logic, policy);
|
|
389
|
+
if (preflightReason) {
|
|
390
|
+
return {
|
|
391
|
+
content: [{ type: "text", text: preflightReason }],
|
|
392
|
+
isError: true,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
return await this.executeInWorkerPool(args, logic, name);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (!logic) {
|
|
399
|
+
if (respectPlainToolPayload()) {
|
|
400
|
+
return await handler(args, _extra);
|
|
401
|
+
}
|
|
402
|
+
stats.failures++;
|
|
403
|
+
stats.lastAttempt = now;
|
|
404
|
+
this.connectionStats.set(clientId, stats);
|
|
405
|
+
return {
|
|
406
|
+
content: [
|
|
407
|
+
{
|
|
408
|
+
type: "text",
|
|
409
|
+
text: 'Error: Malformed payload. Missing LIOP_MAGIC, MANIFEST, or boundaries.\nYou MUST wrap your logic exactly like this:\n\nLIOP_MAGIC:0x00FF\nMANIFEST:{"target":"wasi_v1","name":"DynamicAudit","integrity_checks":true}\n---BEGIN_LOGIC---\n// Your JS code here\n---END_LOGIC---',
|
|
410
|
+
},
|
|
411
|
+
],
|
|
412
|
+
isError: true,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
try {
|
|
416
|
+
// Logic check already performed above, extraction is guaranteed at this point.
|
|
417
|
+
// biome-ignore lint/style/noNonNullAssertion: safe extraction after check
|
|
418
|
+
const logic = this.extractLogic(args.payload);
|
|
419
|
+
// Extract pure logic and deliver it to the developer's function
|
|
420
|
+
args.payload = logic;
|
|
421
|
+
// DELEGATE TO WORKER POOL: Parallel PQC & Sandboxing (Includes PII Shield)
|
|
422
|
+
const preflightReason = this.runPreflightPolicy(name, logic, policy);
|
|
423
|
+
if (preflightReason) {
|
|
424
|
+
stats.failures++;
|
|
425
|
+
stats.lastAttempt = now;
|
|
426
|
+
this.connectionStats.set(clientId, stats);
|
|
427
|
+
return {
|
|
428
|
+
content: [{ type: "text", text: preflightReason }],
|
|
429
|
+
isError: true,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
const result = await this.executeInWorkerPool(args, logic, name);
|
|
433
|
+
if (!result.isError) {
|
|
434
|
+
this.connectionStats.set(clientId, {
|
|
435
|
+
failures: 0,
|
|
436
|
+
lastAttempt: now,
|
|
437
|
+
});
|
|
438
|
+
this.logicCache.set(payloadHash, {
|
|
439
|
+
hash: payloadHash,
|
|
440
|
+
timestamp: now,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
stats.failures++;
|
|
445
|
+
stats.lastAttempt = now;
|
|
446
|
+
this.connectionStats.set(clientId, stats);
|
|
447
|
+
}
|
|
448
|
+
return result;
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
const e = error;
|
|
452
|
+
stats.failures++;
|
|
453
|
+
stats.lastAttempt = now;
|
|
454
|
+
this.connectionStats.set(clientId, stats);
|
|
455
|
+
return {
|
|
456
|
+
content: [
|
|
457
|
+
{ type: "text", text: `ExecutionRuntimeException: ${e.message}` },
|
|
458
|
+
],
|
|
459
|
+
isError: true,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
const inputSchema = {
|
|
465
|
+
type: "object",
|
|
466
|
+
properties: generatedSchema.properties || {},
|
|
467
|
+
required: generatedSchema.required,
|
|
468
|
+
};
|
|
469
|
+
this.tools.set(name, {
|
|
470
|
+
tool: { name, description: finalDescription, inputSchema },
|
|
471
|
+
handler: finalHandler,
|
|
472
|
+
schema,
|
|
473
|
+
policy,
|
|
474
|
+
});
|
|
475
|
+
// [LIOP-ALPHA] Auto-announce capability to the Mesh P2P DHT if node is active
|
|
476
|
+
if (this.meshNode) {
|
|
477
|
+
this.meshNode.announceCapability(name).catch((err) => {
|
|
478
|
+
log.info(`[LIOP-Mesh] Failed to auto-announce tool ${name}: ${err.message}`);
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Register a dynamic prompt
|
|
484
|
+
*/
|
|
485
|
+
prompt(name, description, args, handler) {
|
|
486
|
+
if (this.prompts.has(name)) {
|
|
487
|
+
throw new Error(`Prompt already registered: ${name}`);
|
|
488
|
+
}
|
|
489
|
+
this.prompts.set(name, {
|
|
490
|
+
prompt: { name, description, arguments: args },
|
|
491
|
+
handler,
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Enables LIOP Zero-Shot Autonomy by registering the Blind Analyst standard prompt.
|
|
496
|
+
*/
|
|
497
|
+
enableZeroShotAutonomy() {
|
|
498
|
+
this.prompt("liop_blind_analyst", "The official Logic-Injection-on-Origin Protocol system prompt. Instructs the LLM on how to securely inject Logic-on-Origin without violating PII or safety constraints.", [], (_request) => {
|
|
499
|
+
return {
|
|
500
|
+
description: "LIOP Blind Analyst Instructions",
|
|
501
|
+
messages: [
|
|
502
|
+
{
|
|
503
|
+
role: "user",
|
|
504
|
+
content: {
|
|
505
|
+
type: "text",
|
|
506
|
+
text: `You are the "Blind Analyst" operating within the Logic-Injection-on-Origin Protocol (LIOP) ecosystem.
|
|
507
|
+
Your objective is to perform secure Logic-on-Origin injections. You must process remote data without ever requesting its extraction.
|
|
508
|
+
|
|
509
|
+
INDUSTRIAL CONSTRAINTS & PROTOCOL RULES:
|
|
510
|
+
1. DATA PRIVACY: NEVER attempt to export Personally Identifiable Information (PII). The LIOP Egress Shield will block any response containing raw IDs, names, or addresses.
|
|
511
|
+
2. AGGREGATION FIRST: Always prefer returning counts, averages, or anonymized summaries.
|
|
512
|
+
3. PAYLOAD ENCAPSULATION: Your JavaScript payloads MUST strictly adhere to the LIOPv1 Envelope. DO NOT include markdown backticks or leading text inside the 'payload' argument.
|
|
513
|
+
Structure:
|
|
514
|
+
LIOP_MAGIC:0x00FF
|
|
515
|
+
MANIFEST:{"target":"wasi_v1","name":"AnalysisTask","integrity_checks":true}
|
|
516
|
+
---BEGIN_LOGIC---
|
|
517
|
+
// Your JS Code Here
|
|
518
|
+
---END_LOGIC---
|
|
519
|
+
4. RUNTIME SCOPE: The execution environment provides a global 'env' object. Use 'env.records' to access the target dataset.
|
|
520
|
+
5. LOCALIZATION: Format all JSON response keys in the language used by the user in their query (e.g., use Spanish keys if the query is in Spanish).
|
|
521
|
+
6. SCHEMA RIGIDITY: Only use fields defined in the 'Data Dictionary'. Usage of non-existent fields will trigger a sandbox runtime exception.${this.activeSchema
|
|
522
|
+
? `\n\nCURRENT DATA DICTIONARY (STRICT):\n${JSON.stringify(this.activeSchema, null, 2)}`
|
|
523
|
+
: ""}
|
|
524
|
+
|
|
525
|
+
Protocol Adherence is mandatory for successful execution.`,
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
],
|
|
529
|
+
};
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Register a dynamic resource
|
|
534
|
+
*/
|
|
535
|
+
resource(name, uri, description, mimeType, content) {
|
|
536
|
+
if (this.resources.has(uri)) {
|
|
537
|
+
throw new Error(`Resource URI already registered: ${uri}`);
|
|
538
|
+
}
|
|
539
|
+
this.resources.set(uri, { name, uri, description, mimeType, content });
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Broadcasts the Data Dictionary to the LLM prior to code injection.
|
|
543
|
+
*/
|
|
544
|
+
dataDictionary(schema, name = "Global Medical Data Dictionary", uri = "liop://schema/global", description = "Exposes the internal database schema for Zero-Shot Autonomy planning") {
|
|
545
|
+
this.activeSchema = schema;
|
|
546
|
+
// [Token Economy] Retroactively update tool descriptions with schema field references.
|
|
547
|
+
// Extracts actual data property names from the JSON Schema structure.
|
|
548
|
+
const schemaDigest = this.extractSchemaFieldSummary(schema);
|
|
549
|
+
for (const [toolName, entry] of this.tools.entries()) {
|
|
550
|
+
if (entry.schema.shape.payload &&
|
|
551
|
+
entry.schema.shape.payload instanceof z.ZodString &&
|
|
552
|
+
entry.tool.description &&
|
|
553
|
+
!entry.tool.description.includes("Data structure:")) {
|
|
554
|
+
entry.tool.description += `\nData structure: ${schemaDigest}. Full schema: resource ${uri}`;
|
|
555
|
+
this.tools.set(toolName, entry);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
this.resource(name, uri, description, "application/json", JSON.stringify(schema, null, 2));
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Manually invalidates the AST Logic Cache (e.g. for Zero-Day patches).
|
|
562
|
+
*/
|
|
563
|
+
clearAstCache() {
|
|
564
|
+
this.logicCache.clear();
|
|
565
|
+
log.info("[LIOP-SDK] AST Security Cache cleared by Admin.");
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Emulates calling a tool (used locally or via LIOPMcpBridge)
|
|
569
|
+
*/
|
|
570
|
+
async callTool(request) {
|
|
571
|
+
const entry = this.tools.get(request.name);
|
|
572
|
+
if (!entry) {
|
|
573
|
+
throw new Error(`Tool not found: ${request.name}`);
|
|
574
|
+
}
|
|
575
|
+
try {
|
|
576
|
+
// Validate inputs natively with Zod before execution
|
|
577
|
+
const parsedArgs = entry.schema.parse(request.arguments || {});
|
|
578
|
+
// Re-inject the bypass flag if present since Zod might strip unrecognized keys
|
|
579
|
+
if (request.arguments
|
|
580
|
+
?.__liop_bypass_ast_cache === true) {
|
|
581
|
+
parsedArgs.__liop_bypass_ast_cache = true;
|
|
582
|
+
}
|
|
583
|
+
// [LOGIC-ON-ORIGIN] Intercept code injection directly
|
|
584
|
+
if (parsedArgs &&
|
|
585
|
+
typeof parsedArgs.payload === "string") {
|
|
586
|
+
const payload = parsedArgs
|
|
587
|
+
.payload;
|
|
588
|
+
const logic = this.extractLogic(payload);
|
|
589
|
+
if (logic) {
|
|
590
|
+
const preflightReason = this.runPreflightPolicy(request.name, logic, entry.policy);
|
|
591
|
+
if (preflightReason) {
|
|
592
|
+
return {
|
|
593
|
+
content: [{ type: "text", text: preflightReason }],
|
|
594
|
+
isError: true,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
parsedArgs.payload = logic;
|
|
598
|
+
return await this.executeInWorkerPool(parsedArgs, logic, request.name);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
const result = await entry.handler(parsedArgs, {});
|
|
602
|
+
return result;
|
|
603
|
+
}
|
|
604
|
+
catch (error) {
|
|
605
|
+
const e = error;
|
|
606
|
+
if (e instanceof z.ZodError) {
|
|
607
|
+
return {
|
|
608
|
+
content: [{ type: "text", text: `Validation Error: ${e.message}` }],
|
|
609
|
+
isError: true,
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
return {
|
|
613
|
+
content: [
|
|
614
|
+
{ type: "text", text: `Internal Execution Error: ${e.message}` },
|
|
615
|
+
],
|
|
616
|
+
isError: true,
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Retrieves registered tools
|
|
622
|
+
*/
|
|
623
|
+
listTools() {
|
|
624
|
+
return Array.from(this.tools.values()).map((t) => t.tool);
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Retrieves registered prompts
|
|
628
|
+
*/
|
|
629
|
+
listPrompts() {
|
|
630
|
+
return Array.from(this.prompts.values()).map((p) => p.prompt);
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Gets a specific prompt by name
|
|
634
|
+
*/
|
|
635
|
+
async getPrompt(request) {
|
|
636
|
+
const entry = this.prompts.get(request.name);
|
|
637
|
+
if (!entry) {
|
|
638
|
+
throw new Error(`Prompt not found: ${request.name}`);
|
|
639
|
+
}
|
|
640
|
+
return await entry.handler(request);
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Retrieves registered resources
|
|
644
|
+
*/
|
|
645
|
+
listResources() {
|
|
646
|
+
return Array.from(this.resources.values());
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Reads a specific resource by URI
|
|
650
|
+
*/
|
|
651
|
+
async readResource(uri) {
|
|
652
|
+
const resource = this.resources.get(uri);
|
|
653
|
+
if (!resource) {
|
|
654
|
+
throw new Error(`Resource not found: ${uri}`);
|
|
655
|
+
}
|
|
656
|
+
let text = "No description provided";
|
|
657
|
+
if (typeof resource.content === "function") {
|
|
658
|
+
text = await resource.content();
|
|
659
|
+
}
|
|
660
|
+
else if (typeof resource.content === "string") {
|
|
661
|
+
text = resource.content;
|
|
662
|
+
}
|
|
663
|
+
else if (resource.description) {
|
|
664
|
+
text = resource.description;
|
|
665
|
+
}
|
|
666
|
+
return {
|
|
667
|
+
contents: [
|
|
668
|
+
{
|
|
669
|
+
uri: resource.uri,
|
|
670
|
+
mimeType: resource.mimeType || "text/plain",
|
|
671
|
+
text,
|
|
672
|
+
},
|
|
673
|
+
],
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
getServerInfo() {
|
|
677
|
+
return this.serverInfo;
|
|
678
|
+
}
|
|
679
|
+
getMeshNode() {
|
|
680
|
+
return this.meshNode;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Injects data into the secure sandbox context for Logic-on-Origin tools.
|
|
684
|
+
*/
|
|
685
|
+
setSandboxData(records) {
|
|
686
|
+
this.sandboxRecords = records;
|
|
687
|
+
}
|
|
688
|
+
getBoundPort() {
|
|
689
|
+
return this.boundPort;
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Connects to the libp2p Kademlia DHT and announces capabilities.
|
|
693
|
+
* Boots the gRPC server for secure Logic-on-Origin.
|
|
694
|
+
*/
|
|
695
|
+
async connectToMesh(options = {}) {
|
|
696
|
+
const envPort = process.env.LIOP_GRPC_PORT
|
|
697
|
+
? Number.parseInt(process.env.LIOP_GRPC_PORT, 10)
|
|
698
|
+
: undefined;
|
|
699
|
+
const port = options.port ?? envPort ?? 50051;
|
|
700
|
+
// 1. Initialize Mesh Node (Discovery)
|
|
701
|
+
this.meshNode = new MeshNode(options.meshConfig);
|
|
702
|
+
await this.meshNode.start();
|
|
703
|
+
// 2. Register LIOP Manifest Protocol Handler
|
|
704
|
+
// This allows remote peers to query our tool/resource metadata dynamically.
|
|
705
|
+
const meshNodeRef = this.meshNode;
|
|
706
|
+
this.meshNode.registerManifestHandler(() => {
|
|
707
|
+
const tools = this.listTools().map((t) => ({
|
|
708
|
+
name: t.name,
|
|
709
|
+
description: t.description,
|
|
710
|
+
inputSchema: t.inputSchema,
|
|
711
|
+
}));
|
|
712
|
+
const resources = Array.from(this.resources.values()).map((r) => ({
|
|
713
|
+
name: r.name,
|
|
714
|
+
uri: r.uri,
|
|
715
|
+
description: r.description,
|
|
716
|
+
mimeType: r.mimeType,
|
|
717
|
+
text: typeof r.content === "string" ? r.content : r.description,
|
|
718
|
+
}));
|
|
719
|
+
return {
|
|
720
|
+
peerId: meshNodeRef.getPeerId(),
|
|
721
|
+
grpcPort: port,
|
|
722
|
+
tools,
|
|
723
|
+
resources,
|
|
724
|
+
serverInfo: this.serverInfo,
|
|
725
|
+
};
|
|
726
|
+
});
|
|
727
|
+
// 3. Announce local tools to the DHT
|
|
728
|
+
for (const tool of this.listTools()) {
|
|
729
|
+
await this.meshNode.announceCapability(tool.name).catch(log.info);
|
|
730
|
+
}
|
|
731
|
+
// 4. Announce manifest availability
|
|
732
|
+
await this.meshNode.announceManifest().catch(log.info);
|
|
733
|
+
// 5. Initialize gRPC Server (Execution)
|
|
734
|
+
this.rpcServer = new LiopRpcServer();
|
|
735
|
+
this.rpcServer.addService({
|
|
736
|
+
negotiateIntent: (call, callback) => {
|
|
737
|
+
const request = call.request;
|
|
738
|
+
log.info(`[LIOP-RPC] Negotiating intent for capability: ${request.capability_hash}`);
|
|
739
|
+
// Standard dynamic import to avoid potential circularity
|
|
740
|
+
import("../rpc/crypto/kyber.js").then(async ({ Kyber768Wrapper }) => {
|
|
741
|
+
const { publicKey, secretKey } = await Kyber768Wrapper.generateKeyPair();
|
|
742
|
+
const sessionToken = crypto.randomUUID();
|
|
743
|
+
this.sessions.set(sessionToken, {
|
|
744
|
+
capability_hash: request.capability_hash,
|
|
745
|
+
kyber_sk: secretKey,
|
|
746
|
+
});
|
|
747
|
+
callback(null, {
|
|
748
|
+
accepted: true,
|
|
749
|
+
session_token: sessionToken,
|
|
750
|
+
error_message: "",
|
|
751
|
+
kyber_public_key: publicKey,
|
|
752
|
+
});
|
|
753
|
+
});
|
|
754
|
+
},
|
|
755
|
+
executeLogic: async (call) => {
|
|
756
|
+
const request = call.request;
|
|
757
|
+
log.info(`[LIOP-RPC] Executing Logic-on-Origin for session: ${request.session_token}`);
|
|
758
|
+
const session = this.sessions.get(request.session_token);
|
|
759
|
+
if (!session) {
|
|
760
|
+
call.emit("error", {
|
|
761
|
+
code: grpc.status.UNAUTHENTICATED,
|
|
762
|
+
details: "Invalid session token",
|
|
763
|
+
});
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
try {
|
|
767
|
+
// Pass to Worker Pool for PQC Decryption and WASI/V8 execution
|
|
768
|
+
const workerResponse = await this.workerPool.run({
|
|
769
|
+
ciphertext: request.pqc_ciphertext,
|
|
770
|
+
secretKeyObj: Array.from(session.kyber_sk),
|
|
771
|
+
wasmBinary: request.wasm_binary,
|
|
772
|
+
inputs: request.inputs,
|
|
773
|
+
aesNonce: request.aes_nonce,
|
|
774
|
+
records: this.sandboxRecords,
|
|
775
|
+
sessionToken: request.session_token,
|
|
776
|
+
isEncrypted: true,
|
|
777
|
+
});
|
|
778
|
+
let finalOutput;
|
|
779
|
+
try {
|
|
780
|
+
finalOutput =
|
|
781
|
+
typeof workerResponse.output === "string"
|
|
782
|
+
? workerResponse.output
|
|
783
|
+
: JSON.stringify(workerResponse.output);
|
|
784
|
+
// [PROTOCOL TRANSFORMER] Support for Proxied Tool Calls
|
|
785
|
+
const decoded = JSON.parse(finalOutput);
|
|
786
|
+
if (decoded.__liop_proxy_tool) {
|
|
787
|
+
log.info(`[LIOP-RPC] Executing Proxied Tool: ${decoded.__liop_proxy_tool}`);
|
|
788
|
+
const toolResult = await this.callTool({
|
|
789
|
+
name: decoded.__liop_proxy_tool,
|
|
790
|
+
arguments: decoded.__liop_proxy_args || {},
|
|
791
|
+
});
|
|
792
|
+
finalOutput = JSON.stringify(toolResult);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
catch {
|
|
796
|
+
finalOutput = String(workerResponse.output);
|
|
797
|
+
}
|
|
798
|
+
const response = {
|
|
799
|
+
semantic_evidence: finalOutput,
|
|
800
|
+
cryptographic_proof: Buffer.from(workerResponse.image_id || "", "hex"),
|
|
801
|
+
zk_receipt: workerResponse.zk_receipt
|
|
802
|
+
? Buffer.from(workerResponse.zk_receipt, "base64")
|
|
803
|
+
: Buffer.from(""),
|
|
804
|
+
is_error: false,
|
|
805
|
+
};
|
|
806
|
+
// Final PII check for gRPC egress
|
|
807
|
+
const violation = this.piiScanner.scan([
|
|
808
|
+
{ type: "text", text: finalOutput },
|
|
809
|
+
]);
|
|
810
|
+
const aggregationViolation = this.violatesAggregationFirstPolicy(this.unwrapForAggregationPolicyScan(finalOutput));
|
|
811
|
+
if (violation || aggregationViolation) {
|
|
812
|
+
const reason = violation ||
|
|
813
|
+
"Aggregation-First Policy Violation: row-level export blocked. HINT: Use .reduce() to produce a flat {key:value} object. Do NOT use .map() to create arrays of objects.";
|
|
814
|
+
log.info(`[LIOP-RPC] Secure egress blocked in gRPC stream: ${reason}`);
|
|
815
|
+
response.semantic_evidence = `[LIOP] Egress Security Violation. Output blocked due to policy enforcement (${reason}).`;
|
|
816
|
+
response.is_error = true;
|
|
817
|
+
}
|
|
818
|
+
call.write(response, () => {
|
|
819
|
+
call.end();
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
catch (error) {
|
|
823
|
+
const e = error;
|
|
824
|
+
log.error(`[LIOP-RPC] Execution Error: ${e.message}`);
|
|
825
|
+
// Send error response before closing, avoiding "stream closed without results"
|
|
826
|
+
const errorResponse = {
|
|
827
|
+
semantic_evidence: `Execution Error: ${e.message}`,
|
|
828
|
+
cryptographic_proof: Buffer.from(""),
|
|
829
|
+
zk_receipt: Buffer.from(""),
|
|
830
|
+
is_error: true,
|
|
831
|
+
};
|
|
832
|
+
try {
|
|
833
|
+
call.write(errorResponse, () => {
|
|
834
|
+
call.end();
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
catch (_writeErr) {
|
|
838
|
+
call.end();
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
},
|
|
842
|
+
});
|
|
843
|
+
this.boundPort = await this.rpcServer.listen(port);
|
|
844
|
+
log.info(`[LIOP-SDK] Node successfully announced to Mesh. PeerID: ${this.meshNode.getPeerId()}`);
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Internal worker execution with Egress Filtering logic.
|
|
848
|
+
*/
|
|
849
|
+
async executeInWorkerPool(_args, rawPayload, toolName) {
|
|
850
|
+
try {
|
|
851
|
+
// Transparent local execution without dynamic PQC
|
|
852
|
+
const workerResponse = await this.workerPool.run({
|
|
853
|
+
ciphertext: new Uint8Array(0),
|
|
854
|
+
secretKeyObj: Array.from(new Uint8Array(0)),
|
|
855
|
+
kyberPublicKey: new Uint8Array(0),
|
|
856
|
+
wasmBinary: Buffer.from(rawPayload),
|
|
857
|
+
inputs: {},
|
|
858
|
+
records: this.sandboxRecords,
|
|
859
|
+
sessionToken: "local-dev-token",
|
|
860
|
+
isEncrypted: false, // Use plaintext for local Logic-on-Origin injection
|
|
861
|
+
});
|
|
862
|
+
// Standard MCP Content Array
|
|
863
|
+
const textOutput = JSON.stringify({
|
|
864
|
+
computation_result: workerResponse.output,
|
|
865
|
+
image_id: workerResponse.image_id,
|
|
866
|
+
zk_receipt: workerResponse.zk_receipt,
|
|
867
|
+
status: "Worker Pool Execution Success",
|
|
868
|
+
});
|
|
869
|
+
const content = [
|
|
870
|
+
{
|
|
871
|
+
type: "text",
|
|
872
|
+
text: textOutput,
|
|
873
|
+
},
|
|
874
|
+
];
|
|
875
|
+
const toolPolicy = toolName
|
|
876
|
+
? this.tools.get(toolName)?.policy
|
|
877
|
+
: undefined;
|
|
878
|
+
const policyViolation = this.validateOutputPolicy(toolName || "unknown_tool", workerResponse.output, toolPolicy);
|
|
879
|
+
if (policyViolation) {
|
|
880
|
+
log.info(`[LIOP-SDK] Output policy blocked for ${toolName || "unknown_tool"}: ${policyViolation}`);
|
|
881
|
+
return {
|
|
882
|
+
content: [{ type: "text", text: `[LIOP] ${policyViolation}` }],
|
|
883
|
+
isError: true,
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
// Professional PII Protection Guard
|
|
887
|
+
const violation = this.piiScanner.scan(content);
|
|
888
|
+
const aggregationViolation = this.violatesAggregationFirstPolicy(workerResponse.output);
|
|
889
|
+
if (violation || aggregationViolation) {
|
|
890
|
+
const reason = violation ||
|
|
891
|
+
"Aggregation-First Policy Violation: row-level export blocked. HINT: Use .reduce() to produce a flat {key:value} object. Do NOT use .map() to create arrays of objects.";
|
|
892
|
+
log.info(`[LIOP-SDK] Secure egress blocked in local execution: ${reason}`);
|
|
893
|
+
return {
|
|
894
|
+
content: [
|
|
895
|
+
{
|
|
896
|
+
type: "text",
|
|
897
|
+
text: `[LIOP] Egress Security Violation. Output blocked due to policy enforcement (${reason}).`,
|
|
898
|
+
},
|
|
899
|
+
],
|
|
900
|
+
isError: true,
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
return { content };
|
|
904
|
+
}
|
|
905
|
+
catch (error) {
|
|
906
|
+
const e = error;
|
|
907
|
+
return {
|
|
908
|
+
content: [
|
|
909
|
+
{
|
|
910
|
+
type: "text",
|
|
911
|
+
text: `WorkerPoolError: ${e.message || String(error)}`,
|
|
912
|
+
},
|
|
913
|
+
],
|
|
914
|
+
isError: true,
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Safely destroys the worker pool, gRPC server, and Mesh node.
|
|
920
|
+
* Recommended to be called during graceful shutdowns or test teardowns.
|
|
921
|
+
*/
|
|
922
|
+
async close() {
|
|
923
|
+
if (this.workerPool) {
|
|
924
|
+
await this.workerPool.close({ force: true });
|
|
925
|
+
}
|
|
926
|
+
if (this.rpcServer) {
|
|
927
|
+
await this.rpcServer.stop();
|
|
928
|
+
}
|
|
929
|
+
if (this.meshNode) {
|
|
930
|
+
await this.meshNode.stop();
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|