@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.
Files changed (38) hide show
  1. package/README.md +39 -1
  2. package/native/dendrite-bridge-manifest.json +5 -4
  3. package/native/dendrite-bridge.json +1 -1
  4. package/native/include/axon_dendrite_bridge.h +18 -4
  5. package/native/libaxon_dendrite_bridge.so +0 -0
  6. package/package.json +9 -5
  7. package/runtime/easynet-runtime-rs-0.39.29-x86_64-unknown-linux-gnu.tar.gz +0 -0
  8. package/runtime/runtime-bridge-manifest.json +4 -4
  9. package/runtime/runtime-bridge.json +3 -3
  10. package/src/ability_lifecycle.d.ts +12 -1
  11. package/src/ability_lifecycle.js +117 -31
  12. package/src/capability_request.js +3 -1
  13. package/src/dendrite_bridge/bridge.d.ts +10 -2
  14. package/src/dendrite_bridge/bridge.js +75 -14
  15. package/src/dendrite_bridge/ffi.d.ts +4 -0
  16. package/src/dendrite_bridge/ffi.js +194 -18
  17. package/src/dendrite_bridge/types.d.ts +4 -0
  18. package/src/errors.js +9 -3
  19. package/src/index.d.ts +3 -3
  20. package/src/index.js +9 -10
  21. package/src/mcp/server.d.ts +24 -2
  22. package/src/mcp/server.js +218 -18
  23. package/src/mcp/server.test.js +100 -0
  24. package/src/presets/ability_dispatch/workflow.js +8 -30
  25. package/src/presets/remote_control/config.d.ts +3 -0
  26. package/src/presets/remote_control/config.js +22 -24
  27. package/src/presets/remote_control/descriptor.d.ts +36 -0
  28. package/src/presets/remote_control/descriptor.js +267 -11
  29. package/src/presets/remote_control/handlers.d.ts +8 -0
  30. package/src/presets/remote_control/handlers.js +230 -26
  31. package/src/presets/remote_control/kit.d.ts +4 -2
  32. package/src/presets/remote_control/kit.js +106 -1
  33. package/src/presets/remote_control/kit.test.js +994 -0
  34. package/src/presets/remote_control/orchestrator.d.ts +6 -0
  35. package/src/presets/remote_control/orchestrator.js +36 -1
  36. package/src/presets/remote_control/specs.js +217 -61
  37. package/src/receipt.js +6 -3
  38. 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
- const payload = jsonrpcError(null, -32000, `stream error: ${String(error)}`);
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
- write(output, jsonrpcError(null, -32000, `input line exceeds maximum length (${MAX_LINE_LENGTH} bytes)`));
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
- write(output, jsonrpcError(null, -32000, `input line exceeds maximum length (${MAX_LINE_LENGTH} bytes)`));
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
- const response = this.handleRawLine(raw);
55
- if (response !== null) {
56
- if (typeof response.then === "function") {
57
- response.then((resolved) => { if (resolved !== null)
58
- write(output, resolved); }, (error) => { write(output, jsonrpcError(requestId, -32000, String(error))); });
59
- }
60
- else {
61
- write(output, response);
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
- stream.write(`${JSON.stringify(payload)}\n`);
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 { existsSync, mkdirSync, readFileSync } from "node:fs";
28
- import { dirname, resolve } from "node:path";
29
- import { fileURLToPath } from "node:url";
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
- const here = dirname(fileURLToPath(import.meta.url));
82
- const repoRoot = findRepoRootForBridge(here) || process.cwd();
83
- const candidate = resolve(repoRoot, "core/runtime-rs/dendrite-bridge/target/release", defaultBridgeLibName());
84
- if (existsSync(candidate)) {
85
- process.env.EASYNET_DENDRITE_BRIDGE_LIB = candidate;
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.js
5
- // Description: AUTO-GENERATED compiled from config.ts. Runtime configuration for the remote-control preset.
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
- const moduleDir = dirname(fileURLToPath(import.meta.url));
34
- const candidates = [
35
- resolve(moduleDir, "..", "..", "core", "runtime-rs", "dendrite-bridge", "target", "release", defaultNativeLibName()),
36
- resolve(moduleDir, "..", "..", "core", "runtime-rs", "dendrite-bridge", "target", "debug", defaultNativeLibName()),
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 defaultNativeLibName() {
56
- if (process.platform === "darwin") {
57
- return "libaxon_dendrite_bridge.dylib";
58
- }
59
- if (process.platform === "win32") {
60
- return "axon_dendrite_bridge.dll";
61
- }
62
- return "libaxon_dendrite_bridge.so";
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 };