@easynet-run/node 0.27.14 → 0.39.29
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 +39 -1
- package/native/dendrite-bridge-manifest.json +5 -4
- package/native/dendrite-bridge.json +1 -1
- package/native/include/axon_dendrite_bridge.h +18 -4
- package/native/libaxon_dendrite_bridge.so +0 -0
- package/package.json +9 -5
- package/runtime/easynet-runtime-rs-0.39.29-x86_64-unknown-linux-gnu.tar.gz +0 -0
- package/runtime/runtime-bridge-manifest.json +4 -4
- package/runtime/runtime-bridge.json +3 -3
- package/src/ability_lifecycle.d.ts +12 -1
- package/src/ability_lifecycle.js +117 -31
- package/src/capability_request.js +3 -1
- package/src/dendrite_bridge/bridge.d.ts +10 -2
- package/src/dendrite_bridge/bridge.js +75 -14
- package/src/dendrite_bridge/ffi.d.ts +4 -0
- package/src/dendrite_bridge/ffi.js +194 -18
- package/src/dendrite_bridge/types.d.ts +4 -0
- package/src/errors.js +9 -3
- package/src/index.d.ts +3 -3
- package/src/index.js +9 -10
- package/src/mcp/server.d.ts +24 -2
- package/src/mcp/server.js +218 -18
- package/src/mcp/server.test.js +100 -0
- package/src/presets/ability_dispatch/workflow.js +8 -30
- package/src/presets/remote_control/config.d.ts +3 -0
- package/src/presets/remote_control/config.js +22 -24
- package/src/presets/remote_control/descriptor.d.ts +36 -0
- package/src/presets/remote_control/descriptor.js +267 -11
- package/src/presets/remote_control/handlers.d.ts +8 -0
- package/src/presets/remote_control/handlers.js +230 -26
- package/src/presets/remote_control/kit.d.ts +4 -2
- package/src/presets/remote_control/kit.js +106 -1
- package/src/presets/remote_control/kit.test.js +994 -0
- package/src/presets/remote_control/orchestrator.d.ts +6 -0
- package/src/presets/remote_control/orchestrator.js +36 -1
- package/src/presets/remote_control/specs.js +217 -61
- package/src/receipt.js +6 -3
- package/runtime/easynet-runtime-rs-0.27.14-x86_64-unknown-linux-gnu.tar.gz +0 -0
package/src/mcp/server.js
CHANGED
|
@@ -8,6 +8,7 @@ export class StdioMcpServer {
|
|
|
8
8
|
serverName;
|
|
9
9
|
serverVersion;
|
|
10
10
|
_closed = false;
|
|
11
|
+
_pendingWrite = Promise.resolve();
|
|
11
12
|
constructor(provider, options = {}) {
|
|
12
13
|
this.provider = provider;
|
|
13
14
|
this.protocolVersion = options.protocolVersion?.trim() || DEFAULT_PROTOCOL_VERSION;
|
|
@@ -20,11 +21,11 @@ export class StdioMcpServer {
|
|
|
20
21
|
run(input, output) {
|
|
21
22
|
let cache = "";
|
|
22
23
|
input.setEncoding("utf8");
|
|
24
|
+
const writeFn = (payload) => this.enqueueWrite(output, payload);
|
|
23
25
|
input.on("error", (error) => {
|
|
24
26
|
if (this._closed)
|
|
25
27
|
return;
|
|
26
|
-
|
|
27
|
-
write(output, payload);
|
|
28
|
+
void writeFn(jsonrpcError(null, -32000, `stream error: ${String(error)}`));
|
|
28
29
|
});
|
|
29
30
|
input.on("close", () => {
|
|
30
31
|
// no-op: caller controls lifecycle
|
|
@@ -34,7 +35,7 @@ export class StdioMcpServer {
|
|
|
34
35
|
return;
|
|
35
36
|
cache += chunk.toString();
|
|
36
37
|
if (cache.length > MAX_LINE_LENGTH && !cache.includes("\n")) {
|
|
37
|
-
|
|
38
|
+
void writeFn(jsonrpcError(null, -32000, `input line exceeds maximum length (${MAX_LINE_LENGTH} bytes)`));
|
|
38
39
|
cache = "";
|
|
39
40
|
return;
|
|
40
41
|
}
|
|
@@ -47,31 +48,52 @@ export class StdioMcpServer {
|
|
|
47
48
|
if (this._closed)
|
|
48
49
|
break;
|
|
49
50
|
if (raw.length > MAX_LINE_LENGTH) {
|
|
50
|
-
|
|
51
|
+
void writeFn(jsonrpcError(null, -32000, `input line exceeds maximum length (${MAX_LINE_LENGTH} bytes)`));
|
|
51
52
|
continue;
|
|
52
53
|
}
|
|
53
54
|
const requestId = parseJson(raw)?.id ?? null;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
55
|
+
void this.dispatchRequest(raw, writeFn, requestId);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async dispatchRequest(raw, writeFn, fallbackId) {
|
|
60
|
+
try {
|
|
61
|
+
const response = await this.handleRawLine(raw, writeFn);
|
|
62
|
+
if (response !== null && !this._closed) {
|
|
63
|
+
await writeFn(response);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (!this._closed) {
|
|
68
|
+
await writeFn(jsonrpcError(fallbackId, -32000, String(error)));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
enqueueWrite(output, payload) {
|
|
73
|
+
this._pendingWrite = this._pendingWrite
|
|
74
|
+
.catch(() => undefined)
|
|
75
|
+
.then(async () => {
|
|
76
|
+
if (this._closed) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
await write(output, payload);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
this._closed = true;
|
|
84
|
+
throw new Error("failed to write MCP response");
|
|
64
85
|
}
|
|
65
86
|
});
|
|
87
|
+
return this._pendingWrite;
|
|
66
88
|
}
|
|
67
|
-
handleRawLine(raw) {
|
|
89
|
+
handleRawLine(raw, writeFn) {
|
|
68
90
|
const request = parseJson(raw);
|
|
69
91
|
if (request === null) {
|
|
70
92
|
return jsonrpcError(null, -32700, "parse error");
|
|
71
93
|
}
|
|
72
|
-
return this.handleRequest(request);
|
|
94
|
+
return this.handleRequest(request, writeFn);
|
|
73
95
|
}
|
|
74
|
-
handleRequest(request) {
|
|
96
|
+
handleRequest(request, writeFn) {
|
|
75
97
|
const id = request.id;
|
|
76
98
|
const method = typeof request.method === "string" ? request.method : "";
|
|
77
99
|
const params = asMap(request.params);
|
|
@@ -114,6 +136,28 @@ export class StdioMcpServer {
|
|
|
114
136
|
args = {};
|
|
115
137
|
}
|
|
116
138
|
const typedArgs = args;
|
|
139
|
+
// Try streaming path first. When a writeFn is available, chunks are
|
|
140
|
+
// forwarded to the client as JSON-RPC notifications in real time.
|
|
141
|
+
// Otherwise the stream is buffered into a single response (fallback).
|
|
142
|
+
let streamHandle = null;
|
|
143
|
+
try {
|
|
144
|
+
streamHandle = this.provider.handleToolCallStream?.(name, typedArgs) ?? null;
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
return jsonrpcSuccess(id, toolResponse({
|
|
148
|
+
payload: { ok: false, error: String(error) },
|
|
149
|
+
isError: true,
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
if (streamHandle !== null) {
|
|
153
|
+
const maxBytes = resolveMaxBytes(typedArgs.max_bytes);
|
|
154
|
+
if (writeFn) {
|
|
155
|
+
return streamToClient(streamHandle, id, writeFn, maxBytes);
|
|
156
|
+
}
|
|
157
|
+
return consumeStream(streamHandle, maxBytes)
|
|
158
|
+
.then((result) => jsonrpcSuccess(id, toolResponse(result)))
|
|
159
|
+
.catch((error) => jsonrpcSuccess(id, toolResponse(toolErrorResult(error))));
|
|
160
|
+
}
|
|
117
161
|
let maybeResult;
|
|
118
162
|
try {
|
|
119
163
|
maybeResult = this.provider.handleToolCall(name, typedArgs);
|
|
@@ -166,7 +210,34 @@ function asString(value) {
|
|
|
166
210
|
return value.trim();
|
|
167
211
|
}
|
|
168
212
|
function write(stream, payload) {
|
|
169
|
-
|
|
213
|
+
return new Promise((resolve, reject) => {
|
|
214
|
+
const text = `${JSON.stringify(payload)}\n`;
|
|
215
|
+
const onDrain = () => {
|
|
216
|
+
cleanup();
|
|
217
|
+
resolve();
|
|
218
|
+
};
|
|
219
|
+
const onError = (error) => {
|
|
220
|
+
cleanup();
|
|
221
|
+
reject(error);
|
|
222
|
+
};
|
|
223
|
+
const cleanup = () => {
|
|
224
|
+
stream.removeListener("drain", onDrain);
|
|
225
|
+
stream.removeListener("error", onError);
|
|
226
|
+
};
|
|
227
|
+
try {
|
|
228
|
+
if (stream.write(text)) {
|
|
229
|
+
cleanup();
|
|
230
|
+
resolve();
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
stream.once("drain", onDrain);
|
|
234
|
+
stream.once("error", onError);
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
cleanup();
|
|
238
|
+
reject(error);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
170
241
|
}
|
|
171
242
|
function toolResponse(result) {
|
|
172
243
|
const payload = {
|
|
@@ -174,6 +245,12 @@ function toolResponse(result) {
|
|
|
174
245
|
};
|
|
175
246
|
return result.isError ? { ...payload, isError: true } : payload;
|
|
176
247
|
}
|
|
248
|
+
function toolErrorResult(error) {
|
|
249
|
+
return {
|
|
250
|
+
payload: { ok: false, error: String(error) },
|
|
251
|
+
isError: true,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
177
254
|
function jsonrpcSuccess(id, result) {
|
|
178
255
|
return {
|
|
179
256
|
jsonrpc: "2.0",
|
|
@@ -188,3 +265,126 @@ function jsonrpcError(id, code, message) {
|
|
|
188
265
|
error: { code, message },
|
|
189
266
|
};
|
|
190
267
|
}
|
|
268
|
+
function jsonrpcNotification(method, params) {
|
|
269
|
+
return { jsonrpc: "2.0", method, params };
|
|
270
|
+
}
|
|
271
|
+
/** Default maximum total bytes (64 MiB) accepted from a single streamed MCP tool call. */
|
|
272
|
+
const DEFAULT_MAX_STREAM_BYTES = 64 * 1024 * 1024; // 64 MiB
|
|
273
|
+
/** Resolve a per-call max_bytes override. 0 or absent → default. */
|
|
274
|
+
function resolveMaxBytes(raw) {
|
|
275
|
+
if (typeof raw === "number" && raw > 0)
|
|
276
|
+
return raw;
|
|
277
|
+
return DEFAULT_MAX_STREAM_BYTES;
|
|
278
|
+
}
|
|
279
|
+
function formatBytes(n) {
|
|
280
|
+
if (n >= 1024 * 1024 * 1024)
|
|
281
|
+
return `${(n / (1024 * 1024 * 1024)).toFixed(0)} GiB`;
|
|
282
|
+
if (n >= 1024 * 1024)
|
|
283
|
+
return `${(n / (1024 * 1024)).toFixed(0)} MiB`;
|
|
284
|
+
if (n >= 1024)
|
|
285
|
+
return `${(n / 1024).toFixed(0)} KiB`;
|
|
286
|
+
return `${n} bytes`;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Common helper that iterates a stream handle, converts each chunk to UTF-8,
|
|
290
|
+
* validates encoding, enforces a byte limit, and closes the handle when done.
|
|
291
|
+
*
|
|
292
|
+
* - **Streaming mode** (`onChunk` provided): calls `onChunk` for each decoded
|
|
293
|
+
* chunk and does NOT accumulate chunks in memory.
|
|
294
|
+
* - **Buffer mode** (`onChunk` omitted): accumulates all decoded chunks into
|
|
295
|
+
* the returned `chunks` array.
|
|
296
|
+
*/
|
|
297
|
+
async function processStream(handle, maxBytes, onChunk) {
|
|
298
|
+
const result = {
|
|
299
|
+
hadError: null,
|
|
300
|
+
hadInvalidUtf8: false,
|
|
301
|
+
chunkCount: 0,
|
|
302
|
+
chunks: [],
|
|
303
|
+
};
|
|
304
|
+
let totalBytes = 0;
|
|
305
|
+
try {
|
|
306
|
+
for await (const raw of handle.stream) {
|
|
307
|
+
const buf = Buffer.from(raw);
|
|
308
|
+
const text = buf.toString("utf8");
|
|
309
|
+
if (!result.hadInvalidUtf8 && buf.length > 0 && text.includes('\uFFFD')) {
|
|
310
|
+
result.hadInvalidUtf8 = true;
|
|
311
|
+
}
|
|
312
|
+
totalBytes += buf.length;
|
|
313
|
+
if (totalBytes > maxBytes) {
|
|
314
|
+
const suffix = onChunk ? "limit" : "buffer limit";
|
|
315
|
+
result.hadError = `stream output exceeded ${formatBytes(maxBytes)} ${suffix}`;
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
if (onChunk) {
|
|
319
|
+
await onChunk(text, result.chunkCount);
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
result.chunks.push(text);
|
|
323
|
+
}
|
|
324
|
+
result.chunkCount++;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
result.hadError = String(error);
|
|
329
|
+
}
|
|
330
|
+
finally {
|
|
331
|
+
try {
|
|
332
|
+
handle.close();
|
|
333
|
+
}
|
|
334
|
+
catch (e) {
|
|
335
|
+
console.error("mcp: stream close failed:", e);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return result;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Stream tool output to the client in real time using JSON-RPC notifications.
|
|
342
|
+
*
|
|
343
|
+
* Each chunk is sent as an `axon/streamChunk` notification as soon as it
|
|
344
|
+
* arrives from the underlying stream. After the stream completes (or on
|
|
345
|
+
* error) a final JSON-RPC response is returned with a summary.
|
|
346
|
+
*
|
|
347
|
+
* Clients that do not understand `axon/streamChunk` simply ignore the
|
|
348
|
+
* notifications and still receive the final summary response.
|
|
349
|
+
*/
|
|
350
|
+
async function streamToClient(handle, requestId, writeFn, maxBytes = DEFAULT_MAX_STREAM_BYTES) {
|
|
351
|
+
const result = await processStream(handle, maxBytes, async (decoded, seq) => {
|
|
352
|
+
await writeFn(jsonrpcNotification("axon/streamChunk", {
|
|
353
|
+
requestId,
|
|
354
|
+
seq,
|
|
355
|
+
chunk: decoded,
|
|
356
|
+
}));
|
|
357
|
+
});
|
|
358
|
+
const summary = {
|
|
359
|
+
ok: result.hadError === null,
|
|
360
|
+
chunk_count: result.chunkCount,
|
|
361
|
+
streamed: true,
|
|
362
|
+
};
|
|
363
|
+
if (result.hadError)
|
|
364
|
+
summary.error = result.hadError;
|
|
365
|
+
if (result.hadInvalidUtf8)
|
|
366
|
+
summary.contains_invalid_utf8 = true;
|
|
367
|
+
return jsonrpcSuccess(requestId, toolResponse({
|
|
368
|
+
payload: summary,
|
|
369
|
+
isError: result.hadError !== null,
|
|
370
|
+
}));
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Buffer all chunks from a streaming MCP tool handle into a single result.
|
|
374
|
+
* Stops accumulating after `maxBytes` (default 64 MiB) to prevent unbounded
|
|
375
|
+
* memory growth. Always closes the handle when done, even on error.
|
|
376
|
+
*/
|
|
377
|
+
export async function consumeStream(handle, maxBytes = DEFAULT_MAX_STREAM_BYTES) {
|
|
378
|
+
const result = await processStream(handle, maxBytes);
|
|
379
|
+
if (result.hadError !== null) {
|
|
380
|
+
return {
|
|
381
|
+
payload: { ok: false, chunk_count: result.chunks.length, chunks: result.chunks, error: result.hadError },
|
|
382
|
+
isError: true,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
const payload = { ok: true, chunk_count: result.chunks.length, chunks: result.chunks };
|
|
386
|
+
if (result.hadInvalidUtf8) {
|
|
387
|
+
payload.contains_invalid_utf8 = true;
|
|
388
|
+
}
|
|
389
|
+
return { payload, isError: false };
|
|
390
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// EasyNet Axon for AgentNet
|
|
2
|
+
// =========================
|
|
3
|
+
//
|
|
4
|
+
// File: sdk/node/src/mcp/server.test.js
|
|
5
|
+
// Description: JavaScript runtime mirror for `sdk/node/src/mcp/server.test.js` in the published Node SDK artifact set.
|
|
6
|
+
//
|
|
7
|
+
// Protocol Responsibility:
|
|
8
|
+
// - Preserves the exported API surface generated from the canonical TypeScript source module.
|
|
9
|
+
// - Keeps the distributable Node package consistent for consumers that import built artifacts directly.
|
|
10
|
+
//
|
|
11
|
+
// Implementation Approach:
|
|
12
|
+
// - Mirrors the canonical `.ts` module output and should not diverge semantically from source.
|
|
13
|
+
// - Exists as a distribution artifact so package consumers do not need the TypeScript authoring tree at runtime.
|
|
14
|
+
//
|
|
15
|
+
// Usage Contract:
|
|
16
|
+
// - Update this file through the Node SDK build pipeline when the corresponding TypeScript source changes.
|
|
17
|
+
// - Treat manual edits here as temporary at most; source-of-truth behavior lives in the `.ts` module.
|
|
18
|
+
//
|
|
19
|
+
// Architectural Position:
|
|
20
|
+
// - Published artifact boundary for the Node SDK distribution.
|
|
21
|
+
//
|
|
22
|
+
// Author: Silan.Hu
|
|
23
|
+
// Email: silan.hu@u.nus.edu
|
|
24
|
+
// Copyright (c) 2026-2027 easynet. All rights reserved.
|
|
25
|
+
|
|
26
|
+
import assert from "node:assert/strict";
|
|
27
|
+
import { describe, it } from "node:test";
|
|
28
|
+
import { PassThrough } from "node:stream";
|
|
29
|
+
|
|
30
|
+
import { StdioMcpServer } from "./server.js";
|
|
31
|
+
|
|
32
|
+
describe("StdioMcpServer", () => {
|
|
33
|
+
it("keeps buffered stream responses intact when close throws", async () => {
|
|
34
|
+
const originalConsoleError = console.error;
|
|
35
|
+
const consoleErrors = [];
|
|
36
|
+
console.error = (...args) => {
|
|
37
|
+
consoleErrors.push(args);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const server = new StdioMcpServer({
|
|
41
|
+
toolSpecs: () => [],
|
|
42
|
+
handleToolCall: () => ({ payload: { ok: true }, isError: false }),
|
|
43
|
+
handleToolCallStream: () => ({
|
|
44
|
+
stream: {
|
|
45
|
+
async *[Symbol.asyncIterator]() {
|
|
46
|
+
yield Buffer.from("chunk-1");
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
close() {
|
|
50
|
+
throw new Error("close failed");
|
|
51
|
+
},
|
|
52
|
+
}),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const response = await server.handleRequest({
|
|
57
|
+
jsonrpc: "2.0",
|
|
58
|
+
id: 1,
|
|
59
|
+
method: "tools/call",
|
|
60
|
+
params: {
|
|
61
|
+
name: "streamed_tool",
|
|
62
|
+
arguments: {},
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
assert.equal(response.jsonrpc, "2.0");
|
|
67
|
+
const payload = JSON.parse(response.result.content[0].text);
|
|
68
|
+
assert.equal(payload.ok, true);
|
|
69
|
+
assert.deepEqual(payload.chunks, ["chunk-1"]);
|
|
70
|
+
assert.equal(response.result.isError, undefined);
|
|
71
|
+
assert.equal(consoleErrors.length, 1);
|
|
72
|
+
} finally {
|
|
73
|
+
console.error = originalConsoleError;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("serializes writes through drain backpressure", async () => {
|
|
78
|
+
const input = new PassThrough();
|
|
79
|
+
const chunks = [];
|
|
80
|
+
const output = new PassThrough({ highWaterMark: 1 });
|
|
81
|
+
output.on("data", (chunk) => {
|
|
82
|
+
chunks.push(chunk.toString("utf8"));
|
|
83
|
+
});
|
|
84
|
+
const server = new StdioMcpServer({
|
|
85
|
+
toolSpecs: () => [],
|
|
86
|
+
handleToolCall: () => ({ payload: { ok: true }, isError: false }),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
server.run(input, output);
|
|
90
|
+
input.write('{"jsonrpc":"2.0","id":1,"method":"ping"}\n');
|
|
91
|
+
input.write('{"jsonrpc":"2.0","id":2,"method":"ping"}\n');
|
|
92
|
+
input.end();
|
|
93
|
+
|
|
94
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
95
|
+
|
|
96
|
+
assert.equal(chunks.length, 2);
|
|
97
|
+
assert.match(chunks[0], /"id":1/);
|
|
98
|
+
assert.match(chunks[1], /"id":2/);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
// Copyright (c) 2026-2027 easynet. All rights reserved.
|
|
25
25
|
// Node server flow: publish/install/activate media capability on selected client and invoke tasks.
|
|
26
26
|
import { Orchestrator } from "./orchestrator.js";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
27
|
+
import { resolveLibraryPath } from "../../dendrite_bridge/ffi.js";
|
|
28
|
+
import { mkdirSync, readFileSync } from "node:fs";
|
|
29
|
+
import { resolve } from "node:path";
|
|
30
30
|
import { parseArgs, parsePositiveInt } from "./args.js";
|
|
31
31
|
import { readBundleVersion, resolveBundlePath, sha256Prefixed } from "./bundle.js";
|
|
32
32
|
import { decodeMediaBase64, defaultOutputDir, persistMediaFile } from "./media.js";
|
|
@@ -53,36 +53,14 @@ function resolveConfig(cfg) {
|
|
|
53
53
|
taskPayloadBuilder: cfg?.taskPayloadBuilder ?? mediaCaptureTaskPayload,
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
|
-
function defaultBridgeLibName() {
|
|
57
|
-
if (process.platform === "darwin")
|
|
58
|
-
return "libaxon_dendrite_bridge.dylib";
|
|
59
|
-
if (process.platform === "win32")
|
|
60
|
-
return "axon_dendrite_bridge.dll";
|
|
61
|
-
return "libaxon_dendrite_bridge.so";
|
|
62
|
-
}
|
|
63
|
-
function findRepoRootForBridge(startDir) {
|
|
64
|
-
let current = resolve(startDir);
|
|
65
|
-
for (let steps = 0; steps < 10; steps += 1) {
|
|
66
|
-
const candidate = resolve(current, "core/runtime-rs/dendrite-bridge/target/release", defaultBridgeLibName());
|
|
67
|
-
if (existsSync(candidate)) {
|
|
68
|
-
return current;
|
|
69
|
-
}
|
|
70
|
-
const parent = dirname(current);
|
|
71
|
-
if (parent === current) {
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
current = parent;
|
|
75
|
-
}
|
|
76
|
-
return "";
|
|
77
|
-
}
|
|
78
56
|
function ensureBridgeLibEnv() {
|
|
79
57
|
if (process.env.EASYNET_DENDRITE_BRIDGE_LIB)
|
|
80
58
|
return;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
59
|
+
try {
|
|
60
|
+
process.env.EASYNET_DENDRITE_BRIDGE_LIB = resolveLibraryPath();
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// Non-fatal: library may be resolved later by the bridge constructor.
|
|
86
64
|
}
|
|
87
65
|
}
|
|
88
66
|
function extractInstallId(lifecycle) {
|
|
@@ -8,9 +8,12 @@ export declare const DEFAULT_SIGNATURE = "__AXON_EPHEMERAL_DO_NOT_USE_IN_PROD__"
|
|
|
8
8
|
export declare const DEFAULT_VERSION = "1.0.0";
|
|
9
9
|
export declare const DEFAULT_INSTALL_TIMEOUT_SECONDS = 45;
|
|
10
10
|
export declare const DEFAULT_EXECUTION_MODE = "sandbox_first";
|
|
11
|
+
export { DEFAULT_MCP_TOOL_STREAM_TIMEOUT_MS } from "../../dendrite_bridge/ffi.js";
|
|
11
12
|
export declare function loadRemoteControlConfigFromEnv(): RemoteControlRuntimeConfig;
|
|
12
13
|
export declare function loadConfigFromEnv(): RemoteControlRuntimeConfig;
|
|
13
14
|
export declare function ensureRemoteControlNativeLibEnv(): void;
|
|
14
15
|
export declare function ensureNativeLibEnv(): void;
|
|
15
16
|
export declare function parsePositiveInt(raw: string | undefined, fallback: number): number;
|
|
16
17
|
export declare function asString(raw: unknown, fallback?: string): string;
|
|
18
|
+
export declare function asBool(raw: unknown): boolean;
|
|
19
|
+
export declare function asNumber(raw: unknown): number;
|
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
// EasyNet Axon for AgentNet
|
|
2
2
|
// =========================
|
|
3
3
|
//
|
|
4
|
-
// File: sdk/node/src/presets/remote_control/config.
|
|
5
|
-
// Description:
|
|
4
|
+
// File: sdk/node/src/presets/remote_control/config.ts
|
|
5
|
+
// Description: Runtime configuration and constants for the remote-control preset.
|
|
6
6
|
//
|
|
7
7
|
// Author: Silan.Hu
|
|
8
8
|
// Email: silan.hu@u.nus.edu
|
|
9
9
|
// Copyright (c) 2026-2027 easynet. All rights reserved.
|
|
10
|
-
|
|
11
|
-
import { existsSync } from "node:fs";
|
|
12
|
-
import { dirname, resolve } from "node:path";
|
|
13
|
-
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { resolveLibraryPath } from "../../dendrite_bridge/ffi.js";
|
|
14
11
|
export const DEFAULT_SIGNATURE = "__AXON_EPHEMERAL_DO_NOT_USE_IN_PROD__";
|
|
15
12
|
export const DEFAULT_VERSION = "1.0.0";
|
|
16
13
|
export const DEFAULT_INSTALL_TIMEOUT_SECONDS = 45;
|
|
17
14
|
export const DEFAULT_EXECUTION_MODE = "sandbox_first";
|
|
15
|
+
export { DEFAULT_MCP_TOOL_STREAM_TIMEOUT_MS } from "../../dendrite_bridge/ffi.js";
|
|
18
16
|
export function loadRemoteControlConfigFromEnv() {
|
|
19
17
|
return {
|
|
20
18
|
endpoint: process.env.AXON_ENDPOINT ?? "http://127.0.0.1:50051",
|
|
@@ -30,16 +28,11 @@ export function ensureRemoteControlNativeLibEnv() {
|
|
|
30
28
|
if (process.env.EASYNET_DENDRITE_BRIDGE_LIB) {
|
|
31
29
|
return;
|
|
32
30
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
for (const candidate of candidates) {
|
|
39
|
-
if (existsSync(candidate)) {
|
|
40
|
-
process.env.EASYNET_DENDRITE_BRIDGE_LIB = candidate;
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
31
|
+
try {
|
|
32
|
+
process.env.EASYNET_DENDRITE_BRIDGE_LIB = resolveLibraryPath();
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Non-fatal: library may be resolved later by the bridge constructor.
|
|
43
36
|
}
|
|
44
37
|
}
|
|
45
38
|
export function ensureNativeLibEnv() {
|
|
@@ -52,12 +45,17 @@ export function parsePositiveInt(raw, fallback) {
|
|
|
52
45
|
export function asString(raw, fallback = "") {
|
|
53
46
|
return raw == null ? fallback : String(raw).trim() || fallback;
|
|
54
47
|
}
|
|
55
|
-
function
|
|
56
|
-
if (
|
|
57
|
-
return
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
48
|
+
export function asBool(raw) {
|
|
49
|
+
if (typeof raw === "boolean")
|
|
50
|
+
return raw;
|
|
51
|
+
if (typeof raw === "number")
|
|
52
|
+
return raw !== 0;
|
|
53
|
+
const normalized = String(raw ?? "").trim().toLowerCase();
|
|
54
|
+
return normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "on";
|
|
55
|
+
}
|
|
56
|
+
export function asNumber(raw) {
|
|
57
|
+
if (typeof raw === "number")
|
|
58
|
+
return raw;
|
|
59
|
+
const value = Number.parseInt(String(raw ?? "0"), 10);
|
|
60
|
+
return Number.isFinite(value) ? value : 0;
|
|
63
61
|
}
|
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
export type AbilityTarget = "claude" | "codex" | "openclaw" | "agent_skills";
|
|
2
|
+
export interface AbilityDescriptorLite {
|
|
3
|
+
name: string;
|
|
4
|
+
toolName: string;
|
|
5
|
+
description: string;
|
|
6
|
+
commandTemplate: string;
|
|
7
|
+
inputSchema: JsonRecord;
|
|
8
|
+
outputSchema: JsonRecord;
|
|
9
|
+
version: string;
|
|
10
|
+
tags: string[];
|
|
11
|
+
resourceUri: string;
|
|
12
|
+
instructions: string;
|
|
13
|
+
inputExamples: JsonRecord[];
|
|
14
|
+
prerequisites: string[];
|
|
15
|
+
contextBindings: Record<string, string>;
|
|
16
|
+
category: string;
|
|
17
|
+
}
|
|
18
|
+
export interface AbilityExportResult {
|
|
19
|
+
abilityName: string;
|
|
20
|
+
abilityMd: string;
|
|
21
|
+
invokeScript: string;
|
|
22
|
+
}
|
|
1
23
|
export interface JsonRecord {
|
|
2
24
|
[key: string]: unknown;
|
|
3
25
|
}
|
|
@@ -31,4 +53,18 @@ export declare function defaultOutputSchema(): JsonRecord;
|
|
|
31
53
|
export declare function serializeDescriptor(descriptor: AbilityPackageDescriptor): JsonRecord;
|
|
32
54
|
export declare function requireString(payload: JsonRecord, key: string): string;
|
|
33
55
|
export declare function parseDescriptor(raw: unknown): AbilityPackageDescriptor;
|
|
56
|
+
/**
|
|
57
|
+
* Build an `AbilityPackageDescriptor` from handler arguments.
|
|
58
|
+
*
|
|
59
|
+
* Normalises names, generates a package ID, encodes the command template
|
|
60
|
+
* into Base64 package bytes, and serialises agent extension properties
|
|
61
|
+
* (instructions, input_examples, prerequisites, context_bindings, category)
|
|
62
|
+
* into the metadata map under the `mcp.*` prefix.
|
|
63
|
+
*
|
|
64
|
+
* @throws {Error} If `ability_name`, `command_template`, or `signature_base64` is missing.
|
|
65
|
+
*/
|
|
34
66
|
export declare function buildDescriptor(args: JsonRecord, defaultSignature: string): AbilityPackageDescriptor;
|
|
67
|
+
export declare function createAbilityDescriptor(args: JsonRecord): AbilityDescriptorLite;
|
|
68
|
+
declare function parseAbilityTarget(raw: string): AbilityTarget;
|
|
69
|
+
export declare function exportAbilitySkill(descriptor: AbilityDescriptorLite, target?: AbilityTarget, axonEndpoint?: string): AbilityExportResult;
|
|
70
|
+
export { parseAbilityTarget };
|