@easynet-run/node 0.27.14

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 (61) hide show
  1. package/README.md +135 -0
  2. package/native/dendrite-bridge-manifest.json +38 -0
  3. package/native/dendrite-bridge.json +15 -0
  4. package/native/include/axon_dendrite_bridge.h +460 -0
  5. package/native/libaxon_dendrite_bridge.so +0 -0
  6. package/package.json +67 -0
  7. package/runtime/easynet-runtime-rs-0.27.14-x86_64-unknown-linux-gnu.tar.gz +0 -0
  8. package/runtime/runtime-bridge-manifest.json +20 -0
  9. package/runtime/runtime-bridge.json +9 -0
  10. package/src/ability_lifecycle.d.ts +140 -0
  11. package/src/ability_lifecycle.js +525 -0
  12. package/src/capability_request.d.ts +14 -0
  13. package/src/capability_request.js +247 -0
  14. package/src/dendrite_bridge/bridge.d.ts +98 -0
  15. package/src/dendrite_bridge/bridge.js +712 -0
  16. package/src/dendrite_bridge/ffi.d.ts +60 -0
  17. package/src/dendrite_bridge/ffi.js +139 -0
  18. package/src/dendrite_bridge/index.d.ts +3 -0
  19. package/src/dendrite_bridge/index.js +25 -0
  20. package/src/dendrite_bridge/types.d.ts +179 -0
  21. package/src/dendrite_bridge/types.js +23 -0
  22. package/src/dendrite_bridge.d.ts +1 -0
  23. package/src/dendrite_bridge.js +27 -0
  24. package/src/errors.d.ts +83 -0
  25. package/src/errors.js +146 -0
  26. package/src/index.d.ts +55 -0
  27. package/src/index.js +164 -0
  28. package/src/koffi.d.ts +34 -0
  29. package/src/mcp/server.d.ts +29 -0
  30. package/src/mcp/server.js +190 -0
  31. package/src/presets/ability_dispatch/args.d.ts +5 -0
  32. package/src/presets/ability_dispatch/args.js +36 -0
  33. package/src/presets/ability_dispatch/bundle.d.ts +7 -0
  34. package/src/presets/ability_dispatch/bundle.js +102 -0
  35. package/src/presets/ability_dispatch/media.d.ts +6 -0
  36. package/src/presets/ability_dispatch/media.js +48 -0
  37. package/src/presets/ability_dispatch/orchestrator.d.ts +21 -0
  38. package/src/presets/ability_dispatch/orchestrator.js +117 -0
  39. package/src/presets/ability_dispatch/workflow.d.ts +50 -0
  40. package/src/presets/ability_dispatch/workflow.js +333 -0
  41. package/src/presets/ability_dispatch.d.ts +1 -0
  42. package/src/presets/ability_dispatch.js +2 -0
  43. package/src/presets/remote_control/config.d.ts +16 -0
  44. package/src/presets/remote_control/config.js +63 -0
  45. package/src/presets/remote_control/descriptor.d.ts +34 -0
  46. package/src/presets/remote_control/descriptor.js +183 -0
  47. package/src/presets/remote_control/handlers.d.ts +12 -0
  48. package/src/presets/remote_control/handlers.js +279 -0
  49. package/src/presets/remote_control/kit.d.ts +22 -0
  50. package/src/presets/remote_control/kit.js +72 -0
  51. package/src/presets/remote_control/kit.test.js +87 -0
  52. package/src/presets/remote_control/orchestrator.d.ts +28 -0
  53. package/src/presets/remote_control/orchestrator.js +118 -0
  54. package/src/presets/remote_control/specs.d.ts +2 -0
  55. package/src/presets/remote_control/specs.js +152 -0
  56. package/src/presets/remote_control_case.d.ts +7 -0
  57. package/src/presets/remote_control_case.js +3 -0
  58. package/src/receipt.d.ts +46 -0
  59. package/src/receipt.js +98 -0
  60. package/src/tool_adapter.d.ts +90 -0
  61. package/src/tool_adapter.js +169 -0
@@ -0,0 +1 @@
1
+ export { type ModeSpec, type AbilityDispatchConfig, MEDIA_CAPTURE_MODE_SPECS, validateMediaCaptureResult, mediaCaptureMetadataBuilder, mediaCaptureTaskPayload, runCaseFromArgs, runCase, } from "./ability_dispatch/workflow.js";
@@ -0,0 +1,2 @@
1
+ // Barrel re-export for the ability-dispatch case module.
2
+ export { MEDIA_CAPTURE_MODE_SPECS, validateMediaCaptureResult, mediaCaptureMetadataBuilder, mediaCaptureTaskPayload, runCaseFromArgs, runCase, } from "./ability_dispatch/workflow.js";
@@ -0,0 +1,16 @@
1
+ export interface RemoteControlRuntimeConfig {
2
+ endpoint: string;
3
+ tenant: string;
4
+ connectTimeoutMs: number;
5
+ signatureBase64: string;
6
+ }
7
+ export declare const DEFAULT_SIGNATURE = "__AXON_EPHEMERAL_DO_NOT_USE_IN_PROD__";
8
+ export declare const DEFAULT_VERSION = "1.0.0";
9
+ export declare const DEFAULT_INSTALL_TIMEOUT_SECONDS = 45;
10
+ export declare const DEFAULT_EXECUTION_MODE = "sandbox_first";
11
+ export declare function loadRemoteControlConfigFromEnv(): RemoteControlRuntimeConfig;
12
+ export declare function loadConfigFromEnv(): RemoteControlRuntimeConfig;
13
+ export declare function ensureRemoteControlNativeLibEnv(): void;
14
+ export declare function ensureNativeLibEnv(): void;
15
+ export declare function parsePositiveInt(raw: string | undefined, fallback: number): number;
16
+ export declare function asString(raw: unknown, fallback?: string): string;
@@ -0,0 +1,63 @@
1
+ // EasyNet Axon for AgentNet
2
+ // =========================
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.
6
+ //
7
+ // Author: Silan.Hu
8
+ // Email: silan.hu@u.nus.edu
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";
14
+ export const DEFAULT_SIGNATURE = "__AXON_EPHEMERAL_DO_NOT_USE_IN_PROD__";
15
+ export const DEFAULT_VERSION = "1.0.0";
16
+ export const DEFAULT_INSTALL_TIMEOUT_SECONDS = 45;
17
+ export const DEFAULT_EXECUTION_MODE = "sandbox_first";
18
+ export function loadRemoteControlConfigFromEnv() {
19
+ return {
20
+ endpoint: process.env.AXON_ENDPOINT ?? "http://127.0.0.1:50051",
21
+ tenant: process.env.AXON_TENANT ?? "tenant-test",
22
+ connectTimeoutMs: parsePositiveInt(process.env.AXON_CONNECT_TIMEOUT_MS, 5000),
23
+ signatureBase64: process.env.AXON_DEPLOY_SIGNATURE_BASE64 ?? DEFAULT_SIGNATURE,
24
+ };
25
+ }
26
+ export function loadConfigFromEnv() {
27
+ return loadRemoteControlConfigFromEnv();
28
+ }
29
+ export function ensureRemoteControlNativeLibEnv() {
30
+ if (process.env.EASYNET_DENDRITE_BRIDGE_LIB) {
31
+ return;
32
+ }
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
+ }
43
+ }
44
+ }
45
+ export function ensureNativeLibEnv() {
46
+ ensureRemoteControlNativeLibEnv();
47
+ }
48
+ export function parsePositiveInt(raw, fallback) {
49
+ const parsed = Number.parseInt((raw ?? "").trim(), 10);
50
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
51
+ }
52
+ export function asString(raw, fallback = "") {
53
+ return raw == null ? fallback : String(raw).trim() || fallback;
54
+ }
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";
63
+ }
@@ -0,0 +1,34 @@
1
+ export interface JsonRecord {
2
+ [key: string]: unknown;
3
+ }
4
+ export interface AbilityPackageDescriptor {
5
+ abilityName: string;
6
+ packageId: string;
7
+ capabilityName: string;
8
+ toolName: string;
9
+ description: string;
10
+ version: string;
11
+ tags: string[];
12
+ metadata: Record<string, string>;
13
+ signatureBase64: string;
14
+ packageBytesBase64: string;
15
+ digest?: string;
16
+ }
17
+ export declare function resolveTenant(raw: unknown, fallback: string): string;
18
+ export declare function parseBool(raw: unknown): boolean;
19
+ export declare function randomSuffix(length?: number): string;
20
+ export declare function sanitizeId(raw: string): string;
21
+ export declare function shellSingleQuote(raw: string): string;
22
+ /**
23
+ * Wraps a shell command in a `python3 -c` subprocess template that returns
24
+ * structured JSON output with `entries`, `stdout`, `stderr`, and `exit_code`.
25
+ */
26
+ export declare function buildPythonSubprocessTemplate(command: string): string;
27
+ export declare function asStringArray(raw: unknown): string[];
28
+ export declare function normalizeTags(tags: unknown, fallback: string[]): string[];
29
+ export declare function defaultInputSchema(): JsonRecord;
30
+ export declare function defaultOutputSchema(): JsonRecord;
31
+ export declare function serializeDescriptor(descriptor: AbilityPackageDescriptor): JsonRecord;
32
+ export declare function requireString(payload: JsonRecord, key: string): string;
33
+ export declare function parseDescriptor(raw: unknown): AbilityPackageDescriptor;
34
+ export declare function buildDescriptor(args: JsonRecord, defaultSignature: string): AbilityPackageDescriptor;
@@ -0,0 +1,183 @@
1
+ import { DEFAULT_VERSION, asString } from "./config.js";
2
+ export function resolveTenant(raw, fallback) {
3
+ const value = asString(raw);
4
+ return !value || value === "<nil>" ? fallback : value;
5
+ }
6
+ export function parseBool(raw) {
7
+ if (typeof raw === "boolean") {
8
+ return raw;
9
+ }
10
+ if (typeof raw === "number") {
11
+ return raw !== 0;
12
+ }
13
+ const normalized = asString(raw).toLowerCase();
14
+ return normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "on";
15
+ }
16
+ export function randomSuffix(length = 2) {
17
+ return Math.floor(Math.random() * 1e18).toString(36).slice(0, length);
18
+ }
19
+ export function sanitizeId(raw) {
20
+ const token = asString(raw).toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
21
+ return token || "tool";
22
+ }
23
+ export function shellSingleQuote(raw) {
24
+ return `'${raw.replace(/'/g, "'\"'\"'")}'`;
25
+ }
26
+ /**
27
+ * Wraps a shell command in a `python3 -c` subprocess template that returns
28
+ * structured JSON output with `entries`, `stdout`, `stderr`, and `exit_code`.
29
+ */
30
+ export function buildPythonSubprocessTemplate(command) {
31
+ const quoted = JSON.stringify(command);
32
+ const script = [
33
+ "import json,subprocess",
34
+ `cmd = ${quoted}`,
35
+ "proc = subprocess.run(['/bin/sh', '-c', cmd], text=True, capture_output=True)",
36
+ "combined = (proc.stdout + proc.stderr).strip()",
37
+ "print(json.dumps({'entries': [combined], 'command': cmd, 'exit_code': proc.returncode, 'stdout': proc.stdout, 'stderr': proc.stderr}))",
38
+ ].join("; ");
39
+ return `python3 -c ${shellSingleQuote(script)}`;
40
+ }
41
+ export function asStringArray(raw) {
42
+ return Array.isArray(raw) ? raw.map((value) => asString(value)) : [];
43
+ }
44
+ export function normalizeTags(tags, fallback) {
45
+ const result = [];
46
+ const seen = new Set();
47
+ const all = [...fallback, ...asStringArray(tags)];
48
+ for (const tag of all) {
49
+ const value = tag.trim();
50
+ if (!value || seen.has(value)) {
51
+ continue;
52
+ }
53
+ seen.add(value);
54
+ result.push(value);
55
+ }
56
+ return result;
57
+ }
58
+ export function defaultInputSchema() {
59
+ return { type: "object", properties: { entries: { type: "array", items: { type: "string" } } } };
60
+ }
61
+ export function defaultOutputSchema() {
62
+ return {
63
+ type: "object",
64
+ properties: {
65
+ entries: { type: "array", items: { type: "string" } },
66
+ },
67
+ required: ["entries"],
68
+ };
69
+ }
70
+ export function serializeDescriptor(descriptor) {
71
+ const payload = {
72
+ "ability_name": descriptor.abilityName,
73
+ "package_id": descriptor.packageId,
74
+ "capability_name": descriptor.capabilityName,
75
+ "tool_name": descriptor.toolName,
76
+ "description": descriptor.description,
77
+ "version": descriptor.version,
78
+ "tags": descriptor.tags,
79
+ "metadata": descriptor.metadata,
80
+ "signature_base64": descriptor.signatureBase64,
81
+ "package_bytes_base64": descriptor.packageBytesBase64,
82
+ };
83
+ if (descriptor.digest) {
84
+ payload.digest = descriptor.digest;
85
+ }
86
+ return payload;
87
+ }
88
+ export function requireString(payload, key) {
89
+ const value = asString(payload[key]);
90
+ if (!value) {
91
+ throw new Error(`${key} is required`);
92
+ }
93
+ return value;
94
+ }
95
+ export function parseDescriptor(raw) {
96
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
97
+ throw new Error("package must be an object");
98
+ }
99
+ const input = raw;
100
+ const metadataRaw = input.metadata;
101
+ if (!metadataRaw || typeof metadataRaw !== "object" || Array.isArray(metadataRaw)) {
102
+ throw new Error("package.metadata must be an object of string values");
103
+ }
104
+ const metadata = {};
105
+ for (const [k, v] of Object.entries(metadataRaw)) {
106
+ metadata[k] = asString(v);
107
+ }
108
+ const required = ["ability_name", "package_id", "capability_name", "tool_name", "description", "version", "signature_base64", "package_bytes_base64"];
109
+ for (const key of required) {
110
+ if (!asString(input[key])) {
111
+ throw new Error(`package.${key} is required`);
112
+ }
113
+ }
114
+ return {
115
+ abilityName: asString(input.ability_name),
116
+ packageId: asString(input.package_id),
117
+ capabilityName: asString(input.capability_name),
118
+ toolName: asString(input.tool_name),
119
+ description: asString(input.description),
120
+ version: asString(input.version),
121
+ tags: normalizeTags(input.tags, ["mcp", "ability", "gallery"]),
122
+ metadata,
123
+ signatureBase64: asString(input.signature_base64),
124
+ packageBytesBase64: asString(input.package_bytes_base64),
125
+ digest: asString(input.digest) || undefined,
126
+ };
127
+ }
128
+ export function buildDescriptor(args, defaultSignature) {
129
+ const abilityName = requireString(args, "ability_name");
130
+ const commandTemplate = requireString(args, "command_template");
131
+ const now = Date.now();
132
+ const token = sanitizeId(abilityName);
133
+ const version = asString(args.version, DEFAULT_VERSION);
134
+ const toolName = asString(args.tool_name, `ability_${token}`);
135
+ const packageId = asString(args.package_id, `pkg.ability.${token}.${now}`);
136
+ const capabilityName = asString(args.capability_name, `ability_${token}`);
137
+ const description = asString(args.description, `Ability ${abilityName}`);
138
+ const signatureBase64 = asString(args.signature_base64, defaultSignature);
139
+ if (!signatureBase64) {
140
+ throw new Error("signature_base64 is required");
141
+ }
142
+ const inputSchema = typeof args.input_schema === "object" && !Array.isArray(args.input_schema)
143
+ ? args.input_schema
144
+ : defaultInputSchema();
145
+ const outputSchema = typeof args.output_schema === "object" && !Array.isArray(args.output_schema)
146
+ ? args.output_schema
147
+ : defaultOutputSchema();
148
+ const metadata = {
149
+ "mcp.tool_name": toolName,
150
+ "mcp.description": description,
151
+ "mcp.input_schema": JSON.stringify(inputSchema),
152
+ "mcp.output_schema": JSON.stringify(outputSchema),
153
+ "axon.exec.command": commandTemplate,
154
+ "ability.name": abilityName,
155
+ "ability.version": version,
156
+ };
157
+ const payload = {
158
+ kind: "axon.ability.package.v1",
159
+ ability_name: abilityName,
160
+ package_id: packageId,
161
+ capability_name: capabilityName,
162
+ tool_name: toolName,
163
+ description,
164
+ version,
165
+ command_template: commandTemplate,
166
+ input_schema: inputSchema,
167
+ output_schema: outputSchema,
168
+ created_at_unix_ms: now,
169
+ };
170
+ return {
171
+ abilityName,
172
+ packageId,
173
+ capabilityName,
174
+ toolName,
175
+ description,
176
+ version,
177
+ tags: normalizeTags(args.tags, ["mcp", "ability", "gallery"]),
178
+ metadata,
179
+ signatureBase64,
180
+ packageBytesBase64: Buffer.from(JSON.stringify(payload), "utf8").toString("base64"),
181
+ digest: asString(args.digest) || undefined,
182
+ };
183
+ }
@@ -0,0 +1,12 @@
1
+ import type { JsonRecord, RemoteOrchestrator } from "./orchestrator.js";
2
+ import { type AbilityPackageDescriptor } from "./descriptor.js";
3
+ export declare function resolveDescriptor(args: JsonRecord, fallbackSignature: string): AbilityPackageDescriptor;
4
+ export declare function handleDiscoverNodes(orch: RemoteOrchestrator, tenant: string): JsonRecord;
5
+ export declare function handleListRemoteTools(orch: RemoteOrchestrator, tenant: string, args: JsonRecord): JsonRecord;
6
+ export declare function handleCallRemoteTool(orch: RemoteOrchestrator, tenant: string, args: JsonRecord): JsonRecord;
7
+ export declare function handleDisconnectDevice(orch: RemoteOrchestrator, tenant: string, args: JsonRecord): JsonRecord;
8
+ export declare function handleUninstallAbility(orch: RemoteOrchestrator, tenant: string, args: JsonRecord): JsonRecord;
9
+ export declare function handlePackageAbility(tenant: string, args: JsonRecord, signature: string): JsonRecord;
10
+ export declare function handleDeployAbilityPackage(orch: RemoteOrchestrator, tenant: string, args: JsonRecord, signature: string): JsonRecord;
11
+ export declare function handleDeployAbility(orch: RemoteOrchestrator, tenant: string, args: JsonRecord, signature: string): JsonRecord;
12
+ export declare function handleExecuteCommand(orch: RemoteOrchestrator, tenant: string, args: JsonRecord, signature: string): JsonRecord;
@@ -0,0 +1,279 @@
1
+ // EasyNet Axon for AgentNet
2
+ // =========================
3
+ //
4
+ // File: sdk/node/src/presets/remote_control/handlers.ts
5
+ // Description: MCP tool handlers for remote device control workflows.
6
+ //
7
+ // Author: Silan.Hu
8
+ // Email: silan.hu@u.nus.edu
9
+ // Copyright (c) 2026-2027 easynet. All rights reserved.
10
+ import { buildDescriptor, buildPythonSubprocessTemplate, parseBool, parseDescriptor, serializeDescriptor, } from "./descriptor.js";
11
+ import { asString } from "./config.js";
12
+ // ---------------------------------------------------------------------------
13
+ // Architecture: ability_lifecycle vs presets/remote_control
14
+ //
15
+ // ability_lifecycle (SDK user API) ← typed AbilityDescriptor, DeployTrace
16
+ // │
17
+ // ▼
18
+ // presets/remote_control (MCP layer) ← untyped JSON args from MCP dispatch
19
+ // │
20
+ // ▼
21
+ // dendrite_bridge (FFI) ← native C ABI
22
+ //
23
+ // Handlers here receive untyped args from MCP tool dispatch and delegate to
24
+ // the orchestrator for deployment pipelines. The ability_lifecycle module
25
+ // provides the higher-level typed API for SDK consumers.
26
+ //
27
+ // Note: These handlers use the orchestrator directly (not ability_lifecycle)
28
+ // because they need finer-grained control over cleanup and error reporting
29
+ // in the MCP response format.
30
+ // ---------------------------------------------------------------------------
31
+ const INVOCATION_COMPLETED = 5;
32
+ export function resolveDescriptor(args, fallbackSignature) {
33
+ return args.package ? parseDescriptor(args.package) : buildDescriptor(args, fallbackSignature);
34
+ }
35
+ export function handleDiscoverNodes(orch, tenant) {
36
+ const nodes = orch.listNodes("");
37
+ return {
38
+ ok: true,
39
+ tenant_id: tenant,
40
+ count: nodes.length,
41
+ nodes: nodes.map((node) => ({
42
+ node_id: node.node_id,
43
+ online: node.online,
44
+ display_name: node.display_name,
45
+ })),
46
+ };
47
+ }
48
+ export function handleListRemoteTools(orch, tenant, args) {
49
+ const nodeId = String(args.node_id || "").trim();
50
+ const pattern = String(args.name_pattern || "");
51
+ const tools = orch.listMcpTools(pattern, [], nodeId);
52
+ return {
53
+ ok: true,
54
+ tenant_id: tenant,
55
+ count: tools.length,
56
+ node_id: nodeId,
57
+ name_pattern: pattern,
58
+ tools: tools.map((tool) => ({
59
+ tool_name: tool.tool_name,
60
+ description: tool.description,
61
+ capability_name: tool.capability_name,
62
+ available_nodes: tool.available_nodes,
63
+ node_ids: tool.node_ids,
64
+ })),
65
+ };
66
+ }
67
+ export function handleCallRemoteTool(orch, tenant, args) {
68
+ const toolName = String(args.tool_name || "").trim();
69
+ const nodeId = String(args.node_id || "").trim();
70
+ if (!toolName || !nodeId) {
71
+ return {
72
+ ok: false,
73
+ tenant_id: tenant,
74
+ error: !toolName ? "tool_name is required" : "node_id is required",
75
+ };
76
+ }
77
+ const callArgs = typeof args.arguments === "object" && !Array.isArray(args.arguments) ? args.arguments : {};
78
+ const call = orch.callMcpTool(toolName, nodeId, callArgs);
79
+ const failed = asNumber(call.state) !== INVOCATION_COMPLETED || asBool(call.is_error);
80
+ return { ok: !failed, tenant_id: tenant, tool_name: toolName, node_id: nodeId, call };
81
+ }
82
+ export function handleDisconnectDevice(orch, tenant, args) {
83
+ const nodeId = String(args.node_id || "").trim();
84
+ if (!nodeId) {
85
+ return { ok: false, tenant_id: tenant, error: "node_id is required" };
86
+ }
87
+ const reason = String(args.reason || "").trim() || "disconnect_device: requested by agent";
88
+ const response = orch.disconnectDevice(nodeId, reason);
89
+ return {
90
+ ok: true,
91
+ tenant_id: tenant,
92
+ node_id: nodeId,
93
+ status: "disconnected",
94
+ response,
95
+ };
96
+ }
97
+ export function handleUninstallAbility(orch, tenant, args) {
98
+ const nodeId = String(args.node_id || "").trim();
99
+ const installId = String(args.install_id || "").trim();
100
+ if (!nodeId) {
101
+ return { ok: false, tenant_id: tenant, error: "node_id is required" };
102
+ }
103
+ if (!installId) {
104
+ return { ok: false, tenant_id: tenant, node_id: nodeId, error: "install_id is required" };
105
+ }
106
+ const reason = String(args.reason || "").trim() || "uninstall_ability: requested by agent";
107
+ const response = orch.uninstallAbility(nodeId, installId, reason);
108
+ return {
109
+ ok: true,
110
+ tenant_id: tenant,
111
+ node_id: nodeId,
112
+ install_id: installId,
113
+ status: "uninstalled",
114
+ response,
115
+ };
116
+ }
117
+ export function handlePackageAbility(tenant, args, signature) {
118
+ const descriptor = buildDescriptor(args, signature);
119
+ return { ok: true, tenant_id: tenant, package: serializeDescriptor(descriptor) };
120
+ }
121
+ export function handleDeployAbilityPackage(orch, tenant, args, signature) {
122
+ const nodeId = String(args.node_id || "").trim();
123
+ if (!nodeId) {
124
+ return { ok: false, tenant_id: tenant, error: "node_id is required" };
125
+ }
126
+ const descriptor = resolveDescriptor(args, signature);
127
+ const cleanup = parseBool(args.cleanup_on_activate_failure ?? true);
128
+ const deployed = orch.deployAbilityPackage(descriptor, nodeId, cleanup);
129
+ return {
130
+ ok: true,
131
+ tenant_id: tenant,
132
+ node_id: nodeId,
133
+ package: serializeDescriptor(descriptor),
134
+ ...deployed,
135
+ };
136
+ }
137
+ // ---------------------------------------------------------------------------
138
+ // AI-AGENT PRESET HANDLERS
139
+ // handleDeployAbility and handleExecuteCommand are convenience presets designed
140
+ // for AI agent workflows. They wrap the generic package_ability /
141
+ // deploy_ability_package pipeline into single-call operations.
142
+ // ---------------------------------------------------------------------------
143
+ export function handleDeployAbility(orch, tenant, args, signature) {
144
+ const nodeId = String(args.node_id || "").trim();
145
+ if (!nodeId) {
146
+ return { ok: false, tenant_id: tenant, error: "node_id is required" };
147
+ }
148
+ const commandTemplate = String(args.command_template || "").trim();
149
+ if (!commandTemplate) {
150
+ return { ok: false, tenant_id: tenant, node_id: nodeId, error: "command_template is required" };
151
+ }
152
+ const toolName = String(args.tool_name || `tool_${Date.now()}`).trim();
153
+ const descriptor = buildDescriptor({
154
+ ...args,
155
+ ability_name: toolName,
156
+ tool_name: toolName,
157
+ description: String(args.description || `Tool ${toolName}`),
158
+ command_template: commandTemplate,
159
+ }, signature);
160
+ const deploy = orch.deployAbilityPackage(descriptor, nodeId, true);
161
+ return {
162
+ ok: true,
163
+ tenant_id: tenant,
164
+ node_id: nodeId,
165
+ tool_name: descriptor.toolName,
166
+ package_id: descriptor.packageId,
167
+ install_id: deploy.install_id,
168
+ description: String(args.description || `Tool ${toolName}`),
169
+ deploy,
170
+ };
171
+ }
172
+ export function handleExecuteCommand(orch, tenant, args, signature) {
173
+ const nodeId = String(args.node_id || "").trim();
174
+ const command = String(args.command || "").trim();
175
+ if (!nodeId)
176
+ return { ok: false, tenant_id: tenant, error: "node_id is required" };
177
+ if (!command)
178
+ return { ok: false, tenant_id: tenant, node_id: nodeId, error: "command is required" };
179
+ const shouldCleanup = args.cleanup == null ? true : parseBool(args.cleanup);
180
+ const toolName = `cmd_${Date.now()}_${Math.floor(Math.random() * 1e4).toString(16)}`;
181
+ const descriptor = buildDescriptor({
182
+ ability_name: toolName,
183
+ tool_name: toolName,
184
+ description: `execute command: ${command}`,
185
+ command_template: buildPythonSubprocessTemplate(command),
186
+ tags: ["mcp", "ability", "execute-command"],
187
+ signature_base64: signature,
188
+ }, signature);
189
+ try {
190
+ const deploy = orch.deployAbilityPackage(descriptor, nodeId, shouldCleanup);
191
+ const actualTool = String(deploy.tool_name || descriptor.toolName);
192
+ const installId = asString(deploy.install_id);
193
+ const call = orch.callMcpTool(actualTool, nodeId, {});
194
+ const failed = asNumber(call.state) !== INVOCATION_COMPLETED || asBool(call.is_error);
195
+ const error = failed ? asString(call.error) : "";
196
+ return {
197
+ ok: !failed,
198
+ tenant_id: tenant,
199
+ node_id: nodeId,
200
+ tool_name: actualTool,
201
+ command,
202
+ deploy: serializeDescriptor(descriptor),
203
+ call,
204
+ cleanup: shouldCleanup ? orch.cleanupInstalls([{ mode: "execute_command", nodeId, installId }]) : { attempted: false, ok: false, reason: "cleanup disabled" },
205
+ ...(error ? { error } : {}),
206
+ };
207
+ }
208
+ catch (error) {
209
+ const failure = parseDeployFailure(error);
210
+ const cleanup = parseCleanupSummary(failure, orch, nodeId, shouldCleanup);
211
+ return {
212
+ ok: false,
213
+ tenant_id: tenant,
214
+ node_id: nodeId,
215
+ tool_name: toolName,
216
+ command,
217
+ deploy: serializeDescriptor(descriptor),
218
+ install_id: asString(failure.install_id),
219
+ cleanup,
220
+ error: extractErrorMessage(error, failure),
221
+ };
222
+ }
223
+ }
224
+ function asBool(raw) {
225
+ if (typeof raw === "boolean")
226
+ return raw;
227
+ if (typeof raw === "number")
228
+ return raw !== 0;
229
+ const normalized = String(raw ?? "").trim().toLowerCase();
230
+ return normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "on";
231
+ }
232
+ function asNumber(raw) {
233
+ if (typeof raw === "number")
234
+ return raw;
235
+ const value = Number.parseInt(String(raw ?? "0"), 10);
236
+ return Number.isFinite(value) ? value : 0;
237
+ }
238
+ function parseDeployFailure(error) {
239
+ const message = error instanceof Error ? error.message : String(error ?? "");
240
+ try {
241
+ const parsed = JSON.parse(message);
242
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
243
+ return parsed;
244
+ }
245
+ }
246
+ catch {
247
+ // ignore; keep raw.
248
+ }
249
+ return { message: "deploy_ability_package failed", error: message };
250
+ }
251
+ function extractErrorMessage(error, failure) {
252
+ if (typeof failure.message === "string" && failure.message.trim()) {
253
+ return failure.message;
254
+ }
255
+ if (typeof failure.error === "string" && failure.error.trim()) {
256
+ return failure.error;
257
+ }
258
+ return error instanceof Error ? error.message : String(error);
259
+ }
260
+ function parseCleanupSummary(failure, orch, nodeId, shouldCleanup) {
261
+ const cleanupFromFailure = failure.cleanup;
262
+ if (cleanupFromFailure && typeof cleanupFromFailure === "object" && !Array.isArray(cleanupFromFailure)) {
263
+ return cleanupFromFailure;
264
+ }
265
+ if (!shouldCleanup) {
266
+ return { attempted: false, ok: false, reason: "cleanup disabled" };
267
+ }
268
+ const installId = asString(typeof failure.install_id === "string" ? failure.install_id : String(failure.install_id || ""));
269
+ if (!installId) {
270
+ return { attempted: false, ok: false, reason: "install_id empty" };
271
+ }
272
+ return orch.cleanupInstalls([
273
+ {
274
+ mode: "publish_install_activate",
275
+ nodeId,
276
+ installId,
277
+ },
278
+ ]);
279
+ }
@@ -0,0 +1,22 @@
1
+ import type { McpToolProvider, McpToolResult } from "../../mcp/server.js";
2
+ import { type RemoteControlRuntimeConfig } from "./config.js";
3
+ import { type JsonRecord } from "./descriptor.js";
4
+ import { type OrchestratorFactory } from "./orchestrator.js";
5
+ export type { RemoteControlRuntimeConfig } from "./config.js";
6
+ export interface RemoteOrchestratorFactory extends OrchestratorFactory {
7
+ }
8
+ export declare class RemoteControlCaseKit implements McpToolProvider {
9
+ private readonly config;
10
+ private readonly orchestratorFactory;
11
+ constructor(config: RemoteControlRuntimeConfig, orchestratorFactory?: OrchestratorFactory);
12
+ static loadConfigFromEnv(): RemoteControlRuntimeConfig;
13
+ static loadRemoteControlConfigFromEnv(): RemoteControlRuntimeConfig;
14
+ static ensureNativeLibEnv(): void;
15
+ static ensureRemoteControlNativeLibEnv(): void;
16
+ toolSpecs(): Array<JsonRecord>;
17
+ handleToolCall(name: string, args: JsonRecord): McpToolResult;
18
+ private dispatch;
19
+ private withOrchestrator;
20
+ private toolResult;
21
+ private toolResultWithPayload;
22
+ }