@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/README.md CHANGED
@@ -33,6 +33,43 @@ The SDK ships a native Dendrite bridge binary (loaded via `koffi`) that provides
33
33
  npm install @easynet-run/node
34
34
  ```
35
35
 
36
+ ## Platform / Native Bundle Notes
37
+
38
+ - The published npm artifact currently comes from the Linux x86_64 SDK pack, so the embedded native file is host-matched to that pack.
39
+ - If you need other native targets (iOS/Android/macOS/Linux arm64/Windows), use the target SDK pack from GitHub Releases:
40
+ - `sdk-packs-<target>.tar.gz`
41
+ - extract the `node/` directory and install the `*.tgz` artifact from that directory.
42
+ - For local source development:
43
+ - `SDK_VERSION=<version>` (optional; resolver checks this and then fallback `0.1.0` in the same root)
44
+ - `EASYNET_DENDRITE_BRIDGE_LIB` can force an explicit native path.
45
+ - `EASYNET_DENDRITE_BRIDGE_HOME=<pack-root>` can be set to a shared extracted SDK pack path that contains `native/`.
46
+ - set `EASYNET_DENDRITE_BRIDGE_SOURCE=local` if you want preset/CLI helpers to auto-probe local source-tree bridge artifacts.
47
+ - `EASYNET_DENDRITE_BRIDGE_PLATFORM=ios|android|linux|windows|macos` if host/build target mismatch (for example macOS host + iOS pack).
48
+ - For macOS-hosted iOS dev workflows, prefer a pack-native `.a` and explicit `EASYNET_DENDRITE_BRIDGE_LIB` path binding.
49
+
50
+ Target examples:
51
+
52
+ - macOS arm64: `sdk-packs-aarch64-apple-darwin.tar.gz`
53
+ - macOS x86_64: `sdk-packs-x86_64-apple-darwin.tar.gz`
54
+ - iOS arm64 simulator/device: `sdk-packs-aarch64-apple-ios.tar.gz`
55
+ - iOS x86_64 simulator: `sdk-packs-x86_64-apple-ios.tar.gz`
56
+ - Android arm64: `sdk-packs-aarch64-linux-android.tar.gz`
57
+ - Linux arm64: `sdk-packs-aarch64-unknown-linux-gnu.tar.gz`
58
+ - Linux x86_64: `sdk-packs-x86_64-unknown-linux-gnu.tar.gz`
59
+ - Windows x64: `sdk-packs-x86_64-pc-windows-msvc.tar.gz`
60
+
61
+ ## Development
62
+
63
+ Runtime `.js` files and package-facing `.d.ts` files under `src/` are generated locally from the TypeScript sources with `tsc`, but they are intentionally gitignored and must not be committed.
64
+ Edit `src/**/*.ts`, then run:
65
+
66
+ ```bash
67
+ npm run build
68
+ npm run generated:check
69
+ ```
70
+
71
+ `npm pack` and git-based installs run `prepare`/`prepack`, so published tarballs still include the generated runtime files.
72
+
36
73
  ## Quick Start
37
74
 
38
75
  ### Expose an ability
@@ -94,7 +131,7 @@ No public IP required — the local runtime connects outbound to the Hub and rec
94
131
 
95
132
  Full lifecycle management — not just invocation:
96
133
 
97
- - `createAbility()` / `exportAbility()` — define and register abilities with schemas
134
+ - `buildAbilityDescriptor()` / `exportAbility()` — define and register abilities with schemas
98
135
  - `deployToNode()` — install + activate on target nodes
99
136
  - `listAbilities()` / `invokeAbility()` / `uninstallAbility()`
100
137
  - `discoverNodes()` / `executeCommand()` / `disconnectDevice()` / `drainDevice()`
@@ -119,6 +156,7 @@ First-class voice call lifecycle and transport negotiation (19+ FFI bindings):
119
156
 
120
157
  - `startServer()` spawns a local Axon runtime and joins the Hub — all traffic is outbound.
121
158
  - Federated node discovery and cross-network invocation dispatch.
159
+ - Preset status: the dedicated federation preset helper is currently Python-only; Node exposes federation through runtime bootstrap and transport-facing APIs.
122
160
 
123
161
  ### Remote Control & Orchestration
124
162
 
@@ -1,15 +1,15 @@
1
1
  {
2
- "dendrite_bridge_version": "0.27.14",
2
+ "dendrite_bridge_version": "0.39.29",
3
3
  "target": "x86_64-unknown-linux-gnu",
4
4
  "os": "linux",
5
5
  "arch": "x86_64",
6
6
  "library": {
7
7
  "path": "native/libaxon_dendrite_bridge.so",
8
- "sha256": "48d24b376d4c233a4d836f01ad6ede4dd38677a63fd6b687eeabc6a387ee910b"
8
+ "sha256": "65e28058d2937cc457b28eb52e86c8f0ad376ce00c0e524cd49eac3372fb8ddf"
9
9
  },
10
10
  "header": {
11
11
  "path": "native/include/axon_dendrite_bridge.h",
12
- "sha256": "b54e9ce0898c95276e5a2f5752cf7c09255ab04d0ff6c50b1da86f194faf6b75"
12
+ "sha256": "bb6fdce765a6b1c2fb91a1cee037d00f7ec4297c9a1fd03b39fb829edf2d0fc4"
13
13
  },
14
14
  "protocol_coverage": {
15
15
  "rpc_shapes": ["unary", "server_stream", "client_stream", "bidi_stream"],
@@ -29,10 +29,11 @@
29
29
  "axon_dendrite_deploy_mcp_list_dir_json",
30
30
  "axon_dendrite_list_mcp_tools_json",
31
31
  "axon_dendrite_call_mcp_tool_json",
32
+ "axon_dendrite_call_mcp_tool_stream_open_json",
32
33
  "axon_dendrite_uninstall_capability_json",
33
34
  "axon_dendrite_update_mcp_list_dir_json",
34
35
  "axon_dendrite_protocol_coverage_json",
35
36
  "axon_dendrite_string_free"
36
37
  ],
37
- "generated_at_utc": "2026-03-19T17:45:33Z"
38
+ "generated_at_utc": "2026-03-30T16:23:47Z"
38
39
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "language": "node",
3
- "dendrite_bridge_version": "0.27.14",
3
+ "dendrite_bridge_version": "0.39.29",
4
4
  "target": "x86_64-unknown-linux-gnu",
5
5
  "dendrite_manifest_path": "native/dendrite-bridge-manifest.json",
6
6
  "dendrite_library_path": "native/libaxon_dendrite_bridge.so",
@@ -251,6 +251,20 @@ char *axon_dendrite_list_mcp_tools_json(uint64_t handle,
251
251
  char *axon_dendrite_call_mcp_tool_json(uint64_t handle,
252
252
  const char *request_json);
253
253
 
254
+ // Control helper: open streaming MCP tool call. Returns {"stream_handle": <u64>}.
255
+ // Chunks are read via axon_dendrite_stream_next_json.
256
+ // IMPORTANT: Caller MUST call axon_dendrite_stream_close_json on the returned
257
+ // stream_handle to release the background task and channel. Failure to close
258
+ // will leak resources until the session itself is closed.
259
+ // timeout_ms is split as follows:
260
+ // - setup: min(timeout_ms, 30000) — capped at 30s for stream open
261
+ // - per-chunk: timeout_ms — each stream_next call waits up to this value
262
+ // Per-session limit: max 16 concurrent MCP tool streams.
263
+ // input JSON:
264
+ // {"tenant_id":"...","tool_name":"...","target_node_id":"...","arguments_json":{},"timeout_ms":60000}
265
+ char *axon_dendrite_call_mcp_tool_stream_open_json(uint64_t handle,
266
+ const char *request_json);
267
+
254
268
  // Control helper: deactivate (optional) and uninstall installed capability.
255
269
  // input JSON:
256
270
  // {
@@ -392,8 +406,8 @@ char *axon_dendrite_voice_watch_transport_events_json(uint64_t handle,
392
406
  // "path":"/axon.v1.Invocation/WatchInvocation",
393
407
  // "request_base64":"...",
394
408
  // "metadata":{"x-foo":"bar"},
395
- // "timeout_ms":10000,
396
- // "chunk_timeout_ms":30000,
409
+ // "timeout_ms":10000, // setup/open timeout
410
+ // "chunk_timeout_ms":30000, // per-chunk wait timeout
397
411
  // "chunk_buffer_size":64
398
412
  // }
399
413
  // output JSON:
@@ -426,8 +440,8 @@ char *axon_dendrite_stream_close_json(uint64_t stream_handle);
426
440
  // "request_base64":"...",
427
441
  // "request_chunks_base64":["..."],
428
442
  // "metadata":{"x-foo":"bar"},
429
- // "timeout_ms":10000,
430
- // "chunk_timeout_ms":30000,
443
+ // "timeout_ms":10000, // setup/open timeout
444
+ // "chunk_timeout_ms":30000, // per-chunk wait timeout
431
445
  // "chunk_buffer_size":64,
432
446
  // "request_buffer_size":64
433
447
  // }
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@easynet-run/node",
3
- "version": "0.27.14",
3
+ "version": "0.39.29",
4
4
  "description": "EasyNet Axon Node.js SDK (sidecar-first with Dendrite bridge payload).",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://easynet.run",
@@ -15,12 +15,16 @@
15
15
  },
16
16
  "type": "module",
17
17
  "scripts": {
18
- "build": "tsc",
19
- "check": "tsc --noEmit",
18
+ "build": "node ./scripts/clean-generated.mjs && tsc -p tsconfig.json",
19
+ "check": "tsc -p tsconfig.json --noEmit",
20
20
  "types:check": "tsc -p tsconfig.types.json --noEmit",
21
21
  "types:build": "tsc -p tsconfig.types.json",
22
- "types:verify": "npm run types:build && test -f dist/types/index.d.ts && test -f dist/types/dendrite_bridge.d.ts",
23
- "types": "npm run types:check && npm run types:verify"
22
+ "types:verify": "npm run types:build && node ./scripts/verify-dist-types.mjs",
23
+ "generated:check": "npm run build && node ./scripts/verify-generated.mjs",
24
+ "types": "npm run check && npm run types:verify",
25
+ "verify": "npm run types && npm run generated:check",
26
+ "prepare": "npm run build",
27
+ "prepack": "npm run build"
24
28
  },
25
29
  "main": "src/index.js",
26
30
  "types": "src/index.d.ts",
@@ -1,11 +1,11 @@
1
1
  {
2
- "runtime_version": "0.27.14",
2
+ "runtime_version": "0.39.29",
3
3
  "target": "x86_64-unknown-linux-gnu",
4
4
  "os": "linux",
5
5
  "arch": "x86_64",
6
6
  "artifact": {
7
- "path": "runtime/easynet-runtime-rs-0.27.14-x86_64-unknown-linux-gnu.tar.gz",
8
- "sha256": "382b54020ba9caa97e175fdec8e99a45f52209e40d36214f33efc741482e2072",
7
+ "path": "runtime/easynet-runtime-rs-0.39.29-x86_64-unknown-linux-gnu.tar.gz",
8
+ "sha256": "6234d8b5fa7d71e918c3648e8b627b93d767271f6486a003ffe836fb5ea3bfd1",
9
9
  "binary_name": "axon-runtime"
10
10
  },
11
11
  "language_bridge_descriptors": [
@@ -16,5 +16,5 @@
16
16
  "java/runtime-bridge.json",
17
17
  "swift/runtime-bridge.json"
18
18
  ],
19
- "generated_at_utc": "2026-03-19T17:45:33Z"
19
+ "generated_at_utc": "2026-03-30T16:23:47Z"
20
20
  }
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "language": "node",
3
- "runtime_version": "0.27.14",
3
+ "runtime_version": "0.39.29",
4
4
  "target": "x86_64-unknown-linux-gnu",
5
- "runtime_artifact_path": "runtime/easynet-runtime-rs-0.27.14-x86_64-unknown-linux-gnu.tar.gz",
5
+ "runtime_artifact_path": "runtime/easynet-runtime-rs-0.39.29-x86_64-unknown-linux-gnu.tar.gz",
6
6
  "binary_relative_path": "axon-runtime",
7
- "recommended_extract": "tar -xzf runtime/easynet-runtime-rs-0.27.14-x86_64-unknown-linux-gnu.tar.gz -C <runtime_dir>",
7
+ "recommended_extract": "tar -xzf runtime/easynet-runtime-rs-0.39.29-x86_64-unknown-linux-gnu.tar.gz -C <runtime_dir>",
8
8
  "recommended_launch": "AXON_ENFORCE_MTLS=false <runtime_dir>/axon-runtime"
9
9
  }
@@ -3,6 +3,7 @@ import { type DeployTrace } from "./receipt.js";
3
3
  import type { ToolSpec } from "./tool_adapter.js";
4
4
  export interface AbilityDescriptor {
5
5
  name: string;
6
+ toolName: string;
6
7
  description: string;
7
8
  commandTemplate: string;
8
9
  inputSchema: Record<string, unknown>;
@@ -10,6 +11,11 @@ export interface AbilityDescriptor {
10
11
  version: string;
11
12
  tags: string[];
12
13
  resourceUri: string;
14
+ instructions: string;
15
+ inputExamples: unknown[];
16
+ prerequisites: string[];
17
+ contextBindings: Record<string, string>;
18
+ category: string;
13
19
  }
14
20
  export type AbilityTarget = "claude" | "codex" | "openclaw" | "agent_skills";
15
21
  export interface AbilityExportResult {
@@ -71,7 +77,7 @@ export declare function startServer(endpoint?: string, options?: {
71
77
  /** Pre-shared join token used for outbound federation authentication. */
72
78
  hubJoinToken?: string;
73
79
  }): Promise<ServerHandle>;
74
- export declare function createAbility(opts: {
80
+ export declare function buildAbilityDescriptor(opts: {
75
81
  name: string;
76
82
  description: string;
77
83
  commandTemplate: string;
@@ -80,6 +86,11 @@ export declare function createAbility(opts: {
80
86
  version?: string;
81
87
  tags?: string[];
82
88
  resourceUri?: string;
89
+ instructions?: string;
90
+ inputExamples?: unknown[];
91
+ prerequisites?: string[];
92
+ contextBindings?: Record<string, string>;
93
+ category?: string;
83
94
  }): AbilityDescriptor;
84
95
  export declare function toToolSpec(descriptor: AbilityDescriptor): ToolSpec;
85
96
  export declare function exportAbility(descriptor: AbilityDescriptor, target?: AbilityTarget, axonEndpoint?: string): AbilityExportResult;
@@ -1,5 +1,26 @@
1
- // sdk/node/src/ability_lifecycle.ts Ability lifecycle API: create, deploy, and export as Agent Skills.
1
+ // EasyNet Axon for AgentNet
2
+ // =========================
2
3
  //
4
+ // File: sdk/node/src/ability_lifecycle.ts
5
+ // Description: Node SDK ability lifecycle API for descriptor creation, deployment, invocation, and skill export workflows.
6
+ //
7
+ // Protocol Responsibility:
8
+ // - Defines typed ability descriptors, export targets, and deployment helpers exposed to SDK users.
9
+ // - Keeps cross-language ability naming, resource-uri, and tool-spec semantics aligned.
10
+ //
11
+ // Implementation Approach:
12
+ // - Separates pure descriptor/export helpers from bridge-backed deployment and invocation flows.
13
+ // - Uses explicit validation and stable defaults so generated ability artifacts stay deterministic.
14
+ //
15
+ // Usage Contract:
16
+ // - Callers must provide valid command templates, schema objects, and runtime context for deployment helpers.
17
+ // - Returned descriptors and exported skill artifacts should be treated as stable SDK-level contracts.
18
+ //
19
+ // Architectural Position:
20
+ // - High-level SDK facade above preset/orchestrator layers and below application examples.
21
+ //
22
+ // Author: Silan.Hu
23
+ // Email: silan.hu@u.nus.edu
3
24
  // Copyright (c) 2026-2027 easynet. All rights reserved.
4
25
  import { spawn } from "node:child_process";
5
26
  import { webcrypto } from "node:crypto";
@@ -8,7 +29,7 @@ import { existsSync, mkdirSync, openSync } from "node:fs";
8
29
  import { hostname as localHostname } from "node:os";
9
30
  import { join, dirname } from "node:path";
10
31
  import { env } from "node:process";
11
- import { buildPythonSubprocessTemplate } from "./presets/remote_control/descriptor.js";
32
+ import { buildDescriptor as buildRemotePackageDescriptor, buildPythonSubprocessTemplate, serializeDescriptor as serializeRemotePackageDescriptor, } from "./presets/remote_control/descriptor.js";
12
33
  import { AxonConfigError, AxonBridgeError, AxonInvocationError, AxonPartialSuccessError } from "./errors.js";
13
34
  import { DEFAULT_SIGNATURE } from "./presets/remote_control/config.js";
14
35
  import { beginPhase, buildDeployTrace } from "./receipt.js";
@@ -33,7 +54,10 @@ function normalizeAbilityName(raw) {
33
54
  .replace(/[^a-z0-9_\-]/g, "-")
34
55
  .replace(/-{2,}/g, "-")
35
56
  .replace(/^-+|-+$/g, "");
36
- return result || "ability";
57
+ if (!result) {
58
+ throw new AxonConfigError(`identifier contains no valid characters: ${JSON.stringify(raw)}`);
59
+ }
60
+ return result;
37
61
  }
38
62
  function firstNonEmpty(...values) {
39
63
  for (const value of values) {
@@ -209,9 +233,9 @@ export async function startServer(endpoint, options = {}) {
209
233
  };
210
234
  }
211
235
  // ---------------------------------------------------------------------------
212
- // create
236
+ // build
213
237
  // ---------------------------------------------------------------------------
214
- export function createAbility(opts) {
238
+ export function buildAbilityDescriptor(opts) {
215
239
  if (!opts.name?.trim())
216
240
  throw new AxonConfigError("ability name cannot be empty");
217
241
  if (!opts.commandTemplate?.trim())
@@ -219,6 +243,7 @@ export function createAbility(opts) {
219
243
  const token = normalizeAbilityName(opts.name);
220
244
  return {
221
245
  name: opts.name,
246
+ toolName: token,
222
247
  description: opts.description,
223
248
  commandTemplate: opts.commandTemplate,
224
249
  inputSchema: opts.inputSchema ?? { type: "object", properties: {} },
@@ -226,15 +251,19 @@ export function createAbility(opts) {
226
251
  version: opts.version ?? "1.0.0",
227
252
  tags: opts.tags ?? [],
228
253
  resourceUri: opts.resourceUri ?? `easynet:///r/org/${token}`,
254
+ instructions: opts.instructions ?? "",
255
+ inputExamples: opts.inputExamples ?? [],
256
+ prerequisites: opts.prerequisites ?? [],
257
+ contextBindings: opts.contextBindings ?? {},
258
+ category: opts.category ?? "",
229
259
  };
230
260
  }
231
261
  // ---------------------------------------------------------------------------
232
262
  // toToolSpec
233
263
  // ---------------------------------------------------------------------------
234
264
  export function toToolSpec(descriptor) {
235
- const token = normalizeAbilityName(descriptor.name);
236
265
  return {
237
- name: token,
266
+ name: descriptor.toolName,
238
267
  description: descriptor.description,
239
268
  resourceUri: descriptor.resourceUri,
240
269
  parameters: descriptor.inputSchema,
@@ -244,7 +273,7 @@ export function toToolSpec(descriptor) {
244
273
  // exportAbility
245
274
  // ---------------------------------------------------------------------------
246
275
  export function exportAbility(descriptor, target = "agent_skills", axonEndpoint) {
247
- const token = normalizeAbilityName(descriptor.name);
276
+ const token = descriptor.toolName;
248
277
  const endpoint = axonEndpoint ?? `http://127.0.0.1:${DEFAULT_AXON_PORT}`;
249
278
  const invokeScript = generateInvokeScript(descriptor.resourceUri, endpoint);
250
279
  const abilityMd = generateAbilityMd(descriptor, target, token);
@@ -270,9 +299,9 @@ export function exportAbility(descriptor, target = "agent_skills", axonEndpoint)
270
299
  * ```
271
300
  */
272
301
  export async function deployToNode(bridge, tenant, nodeId, descriptor, signature) {
273
- const token = normalizeAbilityName(descriptor.name);
302
+ const token = descriptor.toolName;
274
303
  const abilityId = token;
275
- const pkg = buildDeployPackage({
304
+ const deployArgs = {
276
305
  ability_name: descriptor.name,
277
306
  tool_name: token,
278
307
  description: descriptor.description,
@@ -280,8 +309,20 @@ export async function deployToNode(bridge, tenant, nodeId, descriptor, signature
280
309
  version: descriptor.version,
281
310
  input_schema: descriptor.inputSchema,
282
311
  output_schema: descriptor.outputSchema,
283
- tags: descriptor.tags.length > 0 ? descriptor.tags : undefined,
284
- }, signature);
312
+ };
313
+ if (descriptor.tags.length > 0)
314
+ deployArgs.tags = descriptor.tags;
315
+ if (descriptor.instructions)
316
+ deployArgs.instructions = descriptor.instructions;
317
+ if (descriptor.inputExamples.length > 0)
318
+ deployArgs.input_examples = descriptor.inputExamples;
319
+ if (descriptor.prerequisites.length > 0)
320
+ deployArgs.prerequisites = descriptor.prerequisites;
321
+ if (Object.keys(descriptor.contextBindings).length > 0)
322
+ deployArgs.context_bindings = descriptor.contextBindings;
323
+ if (descriptor.category)
324
+ deployArgs.category = descriptor.category;
325
+ const pkg = buildDeployPackage(deployArgs, signature);
285
326
  const builder = beginPhase("deploy", tenant, nodeId, abilityId);
286
327
  try {
287
328
  const result = await bridge.deployAbilityPackage(tenant, nodeId, pkg);
@@ -308,7 +349,7 @@ export async function listAbilities(bridge, tenant, nodeId) {
308
349
  // invokeAbility
309
350
  // ---------------------------------------------------------------------------
310
351
  export async function invokeAbility(bridge, tenant, nodeId, toolName, args = {}) {
311
- return await bridge.callMcpToolWithArgs(tenant, toolName, nodeId, args);
352
+ return await bridge.callMcpToolWithArgs(tenant, normalizeAbilityName(toolName), nodeId, args);
312
353
  }
313
354
  // ---------------------------------------------------------------------------
314
355
  // uninstallAbility
@@ -374,14 +415,17 @@ export async function forgetAll(bridge, tenant, nodeId, confirm = false, options
374
415
  // without performing any uninstalls.
375
416
  if (dryRun) {
376
417
  const wouldRemove = [];
418
+ const wouldFail = [];
377
419
  for (const tool of tools) {
378
420
  const installId = String(tool.install_id ?? "");
379
- const toolName = String(tool.tool_name ?? "");
380
- if (!installId)
421
+ const toolName = String(tool.tool_name ?? "unknown");
422
+ if (!installId) {
423
+ wouldFail.push({ tool_name: toolName, error: "missing install_id" });
381
424
  continue;
425
+ }
382
426
  wouldRemove.push(toolName);
383
427
  }
384
- return { removed: wouldRemove, removed_count: wouldRemove.length, failed: [], failed_count: 0 };
428
+ return { removed: wouldRemove, removed_count: wouldRemove.length, failed: wouldFail, failed_count: wouldFail.length };
385
429
  }
386
430
  const removed = [];
387
431
  const failed = [];
@@ -428,19 +472,10 @@ export async function listRemoteTools(bridge, tenant, namePattern = "", nodeId =
428
472
  // buildDeployPackage
429
473
  // ---------------------------------------------------------------------------
430
474
  export function buildDeployPackage(args, signature) {
431
- const abilityName = String(args.ability_name ?? "");
432
- const toolName = String(args.tool_name ?? abilityName);
433
- return {
434
- ability_name: abilityName,
435
- tool_name: toolName,
436
- description: args.description ?? "",
437
- command_template: args.command_template ?? "",
438
- input_schema: args.input_schema ?? { type: "object", properties: {} },
439
- output_schema: args.output_schema ?? { type: "object", properties: {} },
440
- version: args.version ?? "1.0.0",
441
- tags: args.tags ?? [],
442
- signature_base64: signature,
443
- };
475
+ return serializeRemotePackageDescriptor(buildRemotePackageDescriptor({
476
+ ...args,
477
+ signature_base64: args.signature_base64 ?? signature,
478
+ }, signature));
444
479
  }
445
480
  // ---------------------------------------------------------------------------
446
481
  // deployPackage
@@ -451,12 +486,21 @@ export async function deployPackage(bridge, tenant, nodeId, packageDescriptor) {
451
486
  // ---------------------------------------------------------------------------
452
487
  // Internal generators
453
488
  // ---------------------------------------------------------------------------
489
+ const SHELL_UNSAFE_RE = /[`$\\"'\n\r;|&<>(){}!#\x00-\x1f]/;
490
+ function sanitizeShellValue(value, label) {
491
+ if (SHELL_UNSAFE_RE.test(value)) {
492
+ throw new AxonConfigError(`${label} contains disallowed shell characters: ${value}`);
493
+ }
494
+ return value;
495
+ }
454
496
  function generateInvokeScript(resourceUri, endpoint) {
497
+ const safeEP = sanitizeShellValue(endpoint, "endpoint");
498
+ const safeURI = sanitizeShellValue(resourceUri, "resource_uri");
455
499
  return `#!/usr/bin/env bash
456
500
  set -euo pipefail
457
- AXON_ENDPOINT="\${AXON_ENDPOINT:-${endpoint}}"
501
+ AXON_ENDPOINT="\${AXON_ENDPOINT:-${safeEP}}"
458
502
  TENANT="\${AXON_TENANT:-default}"
459
- RESOURCE_URI="${resourceUri}"
503
+ RESOURCE_URI="${safeURI}"
460
504
  ARGS="\${1:-{}}"
461
505
  curl -sS -X POST "\${AXON_ENDPOINT}/v1/invoke" \\
462
506
  -H "Content-Type: application/json" \\
@@ -492,6 +536,20 @@ function generateAbilityMd(descriptor, target, token) {
492
536
  lines.push("");
493
537
  lines.push(descriptor.description);
494
538
  lines.push("");
539
+ // Agent extension: instructions
540
+ if (descriptor.instructions) {
541
+ lines.push(descriptor.instructions);
542
+ lines.push("");
543
+ }
544
+ // Agent extension: prerequisites
545
+ if (descriptor.prerequisites.length > 0) {
546
+ lines.push("## Prerequisites");
547
+ lines.push("");
548
+ for (const p of descriptor.prerequisites) {
549
+ lines.push(`- ${p}`);
550
+ }
551
+ lines.push("");
552
+ }
495
553
  lines.push("## Parameters");
496
554
  lines.push("");
497
555
  lines.push("| Name | Type | Required | Description |");
@@ -504,6 +562,31 @@ function generateAbilityMd(descriptor, target, token) {
504
562
  const isRequired = required.includes(name) ? "Yes" : "No";
505
563
  lines.push(`| ${name} | ${propType} | ${isRequired} | ${propDesc} |`);
506
564
  }
565
+ // Agent extension: examples
566
+ if (descriptor.inputExamples.length > 0) {
567
+ lines.push("");
568
+ lines.push("## Examples");
569
+ lines.push("");
570
+ for (let i = 0; i < descriptor.inputExamples.length; i++) {
571
+ lines.push(`**Example ${i + 1}:**`);
572
+ lines.push("");
573
+ lines.push("```json");
574
+ lines.push(JSON.stringify(descriptor.inputExamples[i]));
575
+ lines.push("```");
576
+ lines.push("");
577
+ }
578
+ }
579
+ // Agent extension: context bindings
580
+ if (Object.keys(descriptor.contextBindings).length > 0) {
581
+ lines.push("## Context Bindings");
582
+ lines.push("");
583
+ lines.push("| Key | Value |");
584
+ lines.push("|-----|-------|");
585
+ for (const [k, v] of Object.entries(descriptor.contextBindings)) {
586
+ lines.push(`| \`${k}\` | ${v} |`);
587
+ }
588
+ lines.push("");
589
+ }
507
590
  lines.push("");
508
591
  lines.push("## Invoke");
509
592
  lines.push("");
@@ -520,6 +603,9 @@ function generateAbilityMd(descriptor, target, token) {
520
603
  lines.push("");
521
604
  lines.push(`- **URI**: \`${descriptor.resourceUri}\``);
522
605
  lines.push(`- **Version**: ${descriptor.version}`);
606
+ if (descriptor.category) {
607
+ lines.push(`- **Category**: ${descriptor.category}`);
608
+ }
523
609
  lines.push("");
524
610
  return lines.join("\n");
525
611
  }
@@ -23,8 +23,10 @@
23
23
  // Author: Silan.Hu
24
24
  // Email: silan.hu@u.nus.edu
25
25
  // Copyright (c) 2026-2027 easynet. All rights reserved.
26
+ // DendriteError re-exported from errors.ts to break the circular dependency:
27
+ // dendrite_bridge imports validate/build helpers from this module, so we
28
+ // cannot import from dendrite_bridge here. errors.ts has no such dependency.
26
29
  import { AxonConfigError } from "./errors.js";
27
- // DendriteError kept for backward compatibility; validation now uses AxonConfigError.
28
30
  export class DendriteError extends Error {
29
31
  constructor(message) {
30
32
  super(message);
@@ -1,4 +1,4 @@
1
- import type { DendriteBridgeOptions, DendriteUnaryCallOptions, DendriteAbilityCallOptions, DendriteStreamCallOptions, DendriteClientStreamCallOptions, DendriteBidiStreamCallOptions, DendriteListNodesOptions, DendriteRegisterNodeOptions, DendritePublishCapabilityOptions, DendriteInstallCapabilityOptions, DendriteListA2aAgentsOptions, DendriteSendA2aTaskOptions, DendriteDeployMcpListDirOptions, DendriteListMcpToolsOptions, DendriteCallMcpToolOptions, DendriteUninstallCapabilityOptions, DendriteUpdateMcpListDirOptions, DendriteVoiceCreateCallOptions, DendriteVoiceJoinCallOptions, DendriteVoiceUpdateMediaPathOptions, DendriteVoiceMetricsOptions, DendriteVoiceEndCallOptions, DendriteVoiceWatchOptions, DendriteVoiceCreateTransportSessionOptions, DendriteVoiceDescriptionOptions, DendriteVoiceCandidateOptions, DendriteVoiceEndTransportSessionOptions, DendriteProtocolInvokeOptions } from "./types.js";
1
+ import type { DendriteBridgeOptions, DendriteUnaryCallOptions, DendriteAbilityCallOptions, DendriteStreamCallOptions, DendriteClientStreamCallOptions, DendriteBidiStreamCallOptions, DendriteListNodesOptions, DendriteRegisterNodeOptions, DendritePublishCapabilityOptions, DendriteInstallCapabilityOptions, DendriteListA2aAgentsOptions, DendriteSendA2aTaskOptions, DendriteDeployMcpListDirOptions, DendriteListMcpToolsOptions, DendriteCallMcpToolOptions, DendriteCallMcpToolStreamOptions, DendriteUninstallCapabilityOptions, DendriteUpdateMcpListDirOptions, DendriteVoiceCreateCallOptions, DendriteVoiceJoinCallOptions, DendriteVoiceUpdateMediaPathOptions, DendriteVoiceMetricsOptions, DendriteVoiceEndCallOptions, DendriteVoiceWatchOptions, DendriteVoiceCreateTransportSessionOptions, DendriteVoiceDescriptionOptions, DendriteVoiceCandidateOptions, DendriteVoiceEndTransportSessionOptions, DendriteProtocolInvokeOptions } from "./types.js";
2
2
  import type { DendriteBridgeLib } from "./ffi.js";
3
3
  export declare class DendriteBridge {
4
4
  private readonly lib;
@@ -23,6 +23,7 @@ export declare class DendriteBridge {
23
23
  listNodes(tenantId: string, options?: DendriteListNodesOptions): Record<string, unknown>[];
24
24
  registerNode(tenantId: string, nodeId: string, options?: DendriteRegisterNodeOptions): Record<string, unknown>;
25
25
  deregisterNode(tenantId: string, nodeId: string, reason?: string): Record<string, unknown>;
26
+ drainNode(tenantId: string, nodeId: string, reason?: string): Record<string, unknown>;
26
27
  heartbeat(tenantId: string, nodeId: string): Record<string, unknown>;
27
28
  publishCapability(tenantId: string, packageId: string, capabilityName: string, options: DendritePublishCapabilityOptions): Record<string, unknown>;
28
29
  installCapability(tenantId: string, nodeId: string, packageId: string, options: DendriteInstallCapabilityOptions): Record<string, unknown>;
@@ -33,6 +34,12 @@ export declare class DendriteBridge {
33
34
  deployMcpListDir(tenantId: string, nodeId: string, options?: DendriteDeployMcpListDirOptions): Record<string, unknown>;
34
35
  listMcpTools(tenantId: string, options?: DendriteListMcpToolsOptions): Record<string, unknown>[];
35
36
  callMcpTool(tenantId: string, toolName: string, options?: DendriteCallMcpToolOptions): Record<string, unknown>;
37
+ /**
38
+ * Open a streaming MCP tool call that returns incremental response chunks.
39
+ * Requires native library support for streaming symbols.
40
+ * Uses DEFAULT_MCP_TOOL_STREAM_TIMEOUT_MS (60s) per chunk if no timeout specified.
41
+ */
42
+ callMcpToolStream(tenantId: string, toolName: string, options?: DendriteCallMcpToolStreamOptions): DendriteServerStream;
36
43
  uninstallCapability(tenantId: string, nodeId: string, installId: string, options?: DendriteUninstallCapabilityOptions): Record<string, unknown>;
37
44
  updateMcpListDir(tenantId: string, nodeId: string, options?: DendriteUpdateMcpListDirOptions): Record<string, unknown>;
38
45
  createVoiceCall(tenantId: string, options?: DendriteVoiceCreateCallOptions): Record<string, unknown>;
@@ -69,9 +76,10 @@ export declare class DendriteBridge {
69
76
  export declare class DendriteServerStream implements AsyncIterable<Uint8Array> {
70
77
  private readonly lib;
71
78
  private readonly streamHandle;
79
+ private readonly defaultChunkTimeoutMs;
72
80
  private done;
73
81
  private nativeClosed;
74
- constructor(lib: DendriteBridgeLib, streamHandle: bigint);
82
+ constructor(lib: DendriteBridgeLib, streamHandle: bigint, defaultChunkTimeoutMs?: number);
75
83
  next(timeoutMs?: number): {
76
84
  chunk: Uint8Array | null;
77
85
  done: boolean;