@agentic-robotics/mcp 0.1.3 → 0.2.0
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/dist/benchmark.d.ts +97 -0
- package/dist/benchmark.d.ts.map +1 -0
- package/dist/benchmark.js +330 -0
- package/dist/benchmark.js.map +1 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +79 -0
- package/dist/cli.js.map +1 -0
- package/dist/enhanced-memory.d.ts +118 -0
- package/dist/enhanced-memory.d.ts.map +1 -0
- package/dist/enhanced-memory.js +296 -0
- package/dist/enhanced-memory.js.map +1 -0
- package/dist/enhanced-server.d.ts +122 -0
- package/dist/enhanced-server.d.ts.map +1 -0
- package/dist/enhanced-server.js +361 -0
- package/dist/enhanced-server.js.map +1 -0
- package/dist/flow-orchestrator.d.ts +75 -0
- package/dist/flow-orchestrator.d.ts.map +1 -0
- package/dist/flow-orchestrator.js +350 -0
- package/dist/flow-orchestrator.js.map +1 -0
- package/dist/hybrid-benchmark.d.ts +44 -0
- package/dist/hybrid-benchmark.d.ts.map +1 -0
- package/dist/hybrid-benchmark.js +232 -0
- package/dist/hybrid-benchmark.js.map +1 -0
- package/dist/hybrid-memory.d.ts +81 -0
- package/dist/hybrid-memory.d.ts.map +1 -0
- package/dist/hybrid-memory.js +296 -0
- package/dist/hybrid-memory.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/interface.d.ts +38 -0
- package/dist/interface.d.ts.map +1 -0
- package/dist/interface.js +75 -0
- package/dist/interface.js.map +1 -0
- package/dist/memory.d.ts +52 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +230 -0
- package/dist/memory.js.map +1 -0
- package/dist/optimized-benchmark.d.ts +74 -0
- package/dist/optimized-benchmark.d.ts.map +1 -0
- package/dist/optimized-benchmark.js +231 -0
- package/dist/optimized-benchmark.js.map +1 -0
- package/dist/server.d.ts +55 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +147 -0
- package/dist/server.js.map +1 -0
- package/package.json +3 -2
- package/src/enhanced-server.ts +1 -1
- package/src/flow-orchestrator.ts +177 -117
- package/src/hybrid-memory.ts +2 -2
- package/src/memory.ts +171 -52
- package/src/optimized-benchmark.ts +0 -302
- package/src/optimized-memory.ts +0 -271
package/dist/server.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ROS3 MCP Server implementation - Simplified version
|
|
3
|
+
*/
|
|
4
|
+
import { ROS3Interface } from './interface.js';
|
|
5
|
+
import { AgentDBMemory } from './memory.js';
|
|
6
|
+
export class ROS3McpServer {
|
|
7
|
+
ros3;
|
|
8
|
+
memory;
|
|
9
|
+
name;
|
|
10
|
+
version;
|
|
11
|
+
constructor(config = {}) {
|
|
12
|
+
const { name = 'ros3-mcp-server', version = '1.0.0', dbPath = './ros3-agentdb.db', } = config;
|
|
13
|
+
this.name = name;
|
|
14
|
+
this.version = version;
|
|
15
|
+
// Initialize ROS3 interface
|
|
16
|
+
this.ros3 = new ROS3Interface();
|
|
17
|
+
// Initialize AgentDB memory
|
|
18
|
+
this.memory = new AgentDBMemory(dbPath);
|
|
19
|
+
}
|
|
20
|
+
async start() {
|
|
21
|
+
// Initialize AgentDB
|
|
22
|
+
await this.memory.initialize();
|
|
23
|
+
console.error(`${this.name} v${this.version} started`);
|
|
24
|
+
console.error('AgentDB initialized and ready');
|
|
25
|
+
console.error('');
|
|
26
|
+
console.error('Available tools:');
|
|
27
|
+
console.error(' - move_robot: Move robot to target pose');
|
|
28
|
+
console.error(' - get_pose: Get current robot pose');
|
|
29
|
+
console.error(' - get_status: Get robot status');
|
|
30
|
+
console.error(' - read_lidar: Read LIDAR data');
|
|
31
|
+
console.error(' - detect_objects: Detect objects from camera');
|
|
32
|
+
console.error(' - query_memory: Query past experiences');
|
|
33
|
+
console.error(' - consolidate_skills: Learn from experiences');
|
|
34
|
+
console.error(' - get_memory_stats: Get memory statistics');
|
|
35
|
+
}
|
|
36
|
+
async stop() {
|
|
37
|
+
await this.memory.close();
|
|
38
|
+
console.error(`${this.name} stopped`);
|
|
39
|
+
}
|
|
40
|
+
// Tool implementations
|
|
41
|
+
async moveRobot(params) {
|
|
42
|
+
const { x, y, z, roll, pitch, yaw, speed = 0.5, frame = 'world' } = params;
|
|
43
|
+
const startTime = Date.now();
|
|
44
|
+
try {
|
|
45
|
+
await this.ros3.moveToPose({ x, y, z, roll, pitch, yaw }, speed, frame);
|
|
46
|
+
const latency = Date.now() - startTime;
|
|
47
|
+
await this.memory.storeEpisode({
|
|
48
|
+
sessionId: `motion-${Date.now()}`,
|
|
49
|
+
taskName: 'move_robot',
|
|
50
|
+
confidence: 0.9,
|
|
51
|
+
success: true,
|
|
52
|
+
outcome: `Moved to [${x}, ${y}, ${z}] in ${latency}ms`,
|
|
53
|
+
strategy: `speed_${speed}_frame_${frame}`,
|
|
54
|
+
metadata: { x, y, z, roll, pitch, yaw, speed, frame, latency },
|
|
55
|
+
});
|
|
56
|
+
return `Robot moved to [${x}, ${y}, ${z}] in ${frame} frame. Completed in ${latency}ms.`;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
await this.memory.storeEpisode({
|
|
60
|
+
sessionId: `motion-${Date.now()}`,
|
|
61
|
+
taskName: 'move_robot',
|
|
62
|
+
confidence: 0.0,
|
|
63
|
+
success: false,
|
|
64
|
+
outcome: `Failed: ${error.message}`,
|
|
65
|
+
metadata: { x, y, z, roll, pitch, yaw, speed, frame },
|
|
66
|
+
});
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async getPose() {
|
|
71
|
+
const pose = await this.ros3.getCurrentPose();
|
|
72
|
+
return `Current pose: [${pose.x}, ${pose.y}, ${pose.z}] (roll=${pose.roll}, pitch=${pose.pitch}, yaw=${pose.yaw})`;
|
|
73
|
+
}
|
|
74
|
+
async getStatus() {
|
|
75
|
+
const status = await this.ros3.getStatus();
|
|
76
|
+
return `Robot status: ${status.status}, Health: ${status.health}`;
|
|
77
|
+
}
|
|
78
|
+
async readLidar(params) {
|
|
79
|
+
const { filter = 'all', max_points = 10000 } = params;
|
|
80
|
+
const startTime = Date.now();
|
|
81
|
+
const cloud = await this.ros3.getLidarData(filter, max_points);
|
|
82
|
+
const latency = Date.now() - startTime;
|
|
83
|
+
await this.memory.storeEpisode({
|
|
84
|
+
sessionId: `lidar-${Date.now()}`,
|
|
85
|
+
taskName: 'read_lidar',
|
|
86
|
+
confidence: 1.0,
|
|
87
|
+
success: true,
|
|
88
|
+
outcome: `Retrieved ${cloud.points.length} points in ${latency}ms`,
|
|
89
|
+
metadata: { filter, max_points, num_points: cloud.points.length, latency },
|
|
90
|
+
});
|
|
91
|
+
return `LIDAR data: ${cloud.points.length} points (filter: ${filter}, latency: ${latency}ms)`;
|
|
92
|
+
}
|
|
93
|
+
async detectObjects(params) {
|
|
94
|
+
const { camera, confidence_threshold = 0.5 } = params;
|
|
95
|
+
const startTime = Date.now();
|
|
96
|
+
const detections = await this.ros3.detectObjects(camera, confidence_threshold);
|
|
97
|
+
const latency = Date.now() - startTime;
|
|
98
|
+
await this.memory.storeEpisode({
|
|
99
|
+
sessionId: `vision-${Date.now()}`,
|
|
100
|
+
taskName: 'detect_objects',
|
|
101
|
+
confidence: 0.9,
|
|
102
|
+
success: true,
|
|
103
|
+
outcome: `Detected ${detections.length} objects in ${latency}ms`,
|
|
104
|
+
metadata: { camera, confidence_threshold, num_detections: detections.length, latency },
|
|
105
|
+
});
|
|
106
|
+
return `Detected ${detections.length} objects from ${camera} camera:\n${JSON.stringify(detections, null, 2)}`;
|
|
107
|
+
}
|
|
108
|
+
async queryMemory(params) {
|
|
109
|
+
const { query, k = 5, only_successes = false, min_confidence = 0.0 } = params;
|
|
110
|
+
const result = await this.memory.queryWithContext(query, { k, minConfidence: min_confidence });
|
|
111
|
+
const filteredMemories = only_successes
|
|
112
|
+
? result.memories.filter((m) => m.success)
|
|
113
|
+
: result.memories;
|
|
114
|
+
return `Found ${filteredMemories.length} memories for "${query}":\n${JSON.stringify(filteredMemories, null, 2)}`;
|
|
115
|
+
}
|
|
116
|
+
async consolidateSkills(params) {
|
|
117
|
+
const { min_attempts = 3, min_reward = 0.7, time_window_days = 7 } = params || {};
|
|
118
|
+
await this.memory.consolidateSkills({
|
|
119
|
+
minAttempts: min_attempts,
|
|
120
|
+
minReward: min_reward,
|
|
121
|
+
timeWindowDays: time_window_days,
|
|
122
|
+
});
|
|
123
|
+
return `Skills consolidated (min_attempts: ${min_attempts}, min_reward: ${min_reward}, window: ${time_window_days} days)`;
|
|
124
|
+
}
|
|
125
|
+
async getMemoryStats() {
|
|
126
|
+
const stats = await this.memory.getStats();
|
|
127
|
+
return `AgentDB Statistics:\n${stats.stats}`;
|
|
128
|
+
}
|
|
129
|
+
// Get server info
|
|
130
|
+
getInfo() {
|
|
131
|
+
return {
|
|
132
|
+
name: this.name,
|
|
133
|
+
version: this.version,
|
|
134
|
+
tools: [
|
|
135
|
+
'move_robot',
|
|
136
|
+
'get_pose',
|
|
137
|
+
'get_status',
|
|
138
|
+
'read_lidar',
|
|
139
|
+
'detect_objects',
|
|
140
|
+
'query_memory',
|
|
141
|
+
'consolidate_skills',
|
|
142
|
+
'get_memory_stats',
|
|
143
|
+
],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentic-robotics/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Model Context Protocol server for agentic robotics with AgentDB integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "tsc",
|
|
30
|
+
"prepublishOnly": "npm run build",
|
|
30
31
|
"dev": "tsx src/cli.ts",
|
|
31
32
|
"start": "node dist/cli.js",
|
|
32
33
|
"test": "vitest",
|
|
@@ -34,7 +35,7 @@
|
|
|
34
35
|
"benchmark:hybrid": "tsx src/hybrid-benchmark.ts"
|
|
35
36
|
},
|
|
36
37
|
"dependencies": {
|
|
37
|
-
"@agentic-robotics/core": "^0.
|
|
38
|
+
"@agentic-robotics/core": "^0.2.0",
|
|
38
39
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
39
40
|
"agentdb": "^1.6.1",
|
|
40
41
|
"agentic-flow": "^1.10.2",
|
package/src/enhanced-server.ts
CHANGED
|
@@ -27,7 +27,7 @@ export interface EnhancedROS3McpServerConfig {
|
|
|
27
27
|
export class EnhancedROS3McpServer {
|
|
28
28
|
private ros3: ROS3Interface;
|
|
29
29
|
private memory: EnhancedAgentDBMemory;
|
|
30
|
-
private orchestrator
|
|
30
|
+
private orchestrator?: FlowOrchestrator;
|
|
31
31
|
private name: string;
|
|
32
32
|
private version: string;
|
|
33
33
|
private config: EnhancedROS3McpServerConfig;
|
package/src/flow-orchestrator.ts
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Agentic Flow Orchestration Integration
|
|
2
|
+
* Agentic Flow Orchestration Integration - SECURE VERSION
|
|
3
3
|
*
|
|
4
4
|
* Integrates agentic-flow's 66 agents and 213 MCP tools for
|
|
5
5
|
* multi-robot coordination and complex task execution
|
|
6
|
+
*
|
|
7
|
+
* SECURITY: All command execution uses secure spawn() with argument arrays
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
import { spawn } from 'child_process';
|
|
9
|
-
import { promisify } from 'util';
|
|
10
|
-
import { exec } from 'child_process';
|
|
11
|
-
|
|
12
|
-
const execAsync = promisify(exec);
|
|
13
11
|
|
|
14
12
|
export interface AgentTask {
|
|
15
13
|
id: string;
|
|
@@ -47,12 +45,63 @@ export interface OrchestrationMetrics {
|
|
|
47
45
|
toolUsage: Record<string, number>;
|
|
48
46
|
}
|
|
49
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Secure helper to spawn commands with proper argument handling
|
|
50
|
+
*/
|
|
51
|
+
function spawnAsync(
|
|
52
|
+
command: string,
|
|
53
|
+
args: string[],
|
|
54
|
+
options: { timeout?: number } = {}
|
|
55
|
+
): Promise<{ stdout: string; stderr: string; code: number }> {
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const child = spawn(command, args, {
|
|
58
|
+
shell: false, // Critical: disable shell interpretation
|
|
59
|
+
timeout: options.timeout,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
let stdout = '';
|
|
63
|
+
let stderr = '';
|
|
64
|
+
|
|
65
|
+
child.stdout?.on('data', (data) => {
|
|
66
|
+
stdout += data.toString();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
child.stderr?.on('data', (data) => {
|
|
70
|
+
stderr += data.toString();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
child.on('close', (code) => {
|
|
74
|
+
resolve({ stdout, stderr, code: code || 0 });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
child.on('error', (error) => {
|
|
78
|
+
reject(error);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Validate and sanitize task parameters
|
|
85
|
+
*/
|
|
86
|
+
function validateTaskParams(params: Record<string, any>): void {
|
|
87
|
+
// Check for command injection attempts
|
|
88
|
+
const paramStr = JSON.stringify(params);
|
|
89
|
+
if (paramStr.includes(';') || paramStr.includes('|') || paramStr.includes('&')) {
|
|
90
|
+
throw new Error('Invalid characters in task parameters');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
50
94
|
export class FlowOrchestrator {
|
|
51
95
|
private config: SwarmConfig;
|
|
52
96
|
private metrics: OrchestrationMetrics;
|
|
53
97
|
private initialized: boolean = false;
|
|
54
98
|
|
|
55
99
|
constructor(config: SwarmConfig = {}) {
|
|
100
|
+
// Validate config
|
|
101
|
+
if (config.numAgents && (config.numAgents < 1 || config.numAgents > 200)) {
|
|
102
|
+
throw new Error('numAgents must be between 1 and 200');
|
|
103
|
+
}
|
|
104
|
+
|
|
56
105
|
this.config = {
|
|
57
106
|
numAgents: config.numAgents || 66,
|
|
58
107
|
strategy: config.strategy || 'adaptive',
|
|
@@ -77,16 +126,13 @@ export class FlowOrchestrator {
|
|
|
77
126
|
const startTime = Date.now();
|
|
78
127
|
|
|
79
128
|
try {
|
|
80
|
-
//
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
this.config.reasoningEnabled ? '--enable-reasoning' : '',
|
|
86
|
-
this.config.learningEnabled ? '--enable-learning' : '',
|
|
87
|
-
].filter(Boolean).join(' ');
|
|
129
|
+
// Secure initialization with spawn
|
|
130
|
+
const args = ['agentic-flow', 'init', '--agents', this.config.numAgents!.toString(), '--strategy', this.config.strategy!];
|
|
131
|
+
|
|
132
|
+
if (this.config.reasoningEnabled) args.push('--enable-reasoning');
|
|
133
|
+
if (this.config.learningEnabled) args.push('--enable-learning');
|
|
88
134
|
|
|
89
|
-
await
|
|
135
|
+
await spawnAsync('npx', args);
|
|
90
136
|
|
|
91
137
|
const initTime = Date.now() - startTime;
|
|
92
138
|
console.error(`🌊 Agentic Flow initialized (${this.config.numAgents} agents, ${initTime}ms)`);
|
|
@@ -97,29 +143,45 @@ export class FlowOrchestrator {
|
|
|
97
143
|
}
|
|
98
144
|
}
|
|
99
145
|
|
|
100
|
-
/**
|
|
101
|
-
* Execute a single task using agentic-flow orchestration
|
|
102
|
-
*/
|
|
103
146
|
async executeTask(task: AgentTask): Promise<AgentResult> {
|
|
147
|
+
if (!this.initialized) {
|
|
148
|
+
throw new Error('FlowOrchestrator not initialized. Call initialize() first.');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Validate input
|
|
152
|
+
validateTaskParams(task.params);
|
|
153
|
+
|
|
104
154
|
const startTime = Date.now();
|
|
105
155
|
this.metrics.totalTasks++;
|
|
106
156
|
|
|
107
157
|
try {
|
|
108
|
-
const
|
|
109
|
-
'
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
task.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
158
|
+
const args = [
|
|
159
|
+
'agentic-flow',
|
|
160
|
+
'execute',
|
|
161
|
+
'--task-type',
|
|
162
|
+
task.type,
|
|
163
|
+
'--priority',
|
|
164
|
+
task.priority,
|
|
165
|
+
'--params',
|
|
166
|
+
JSON.stringify(task.params),
|
|
167
|
+
'--format',
|
|
168
|
+
'json',
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
if (task.timeout) args.push('--timeout', task.timeout.toString());
|
|
172
|
+
if (task.retries) args.push('--retries', task.retries.toString());
|
|
173
|
+
if (this.config.reasoningEnabled) args.push('--enable-reasoning');
|
|
174
|
+
|
|
175
|
+
const { stdout, code } = await spawnAsync('npx', args, {
|
|
176
|
+
timeout: task.timeout || 30000,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (code !== 0) {
|
|
180
|
+
throw new Error(`Agentic-flow execution failed with code ${code}`);
|
|
181
|
+
}
|
|
120
182
|
|
|
121
183
|
const lines = stdout.trim().split('\n');
|
|
122
|
-
const jsonLine = lines.find(line => line.startsWith('{'));
|
|
184
|
+
const jsonLine = lines.find((line) => line.startsWith('{'));
|
|
123
185
|
|
|
124
186
|
if (jsonLine) {
|
|
125
187
|
const data = JSON.parse(jsonLine);
|
|
@@ -159,30 +221,41 @@ export class FlowOrchestrator {
|
|
|
159
221
|
}
|
|
160
222
|
}
|
|
161
223
|
|
|
162
|
-
/**
|
|
163
|
-
* Execute multiple tasks in parallel using swarm intelligence
|
|
164
|
-
*/
|
|
165
224
|
async executeSwarm(tasks: AgentTask[]): Promise<AgentResult[]> {
|
|
166
|
-
|
|
225
|
+
if (!this.initialized) {
|
|
226
|
+
throw new Error('FlowOrchestrator not initialized. Call initialize() first.');
|
|
227
|
+
}
|
|
167
228
|
|
|
229
|
+
const startTime = Date.now();
|
|
168
230
|
console.error(`🐝 Executing swarm with ${tasks.length} tasks`);
|
|
169
231
|
|
|
170
232
|
try {
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
'--
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
233
|
+
// Validate all tasks
|
|
234
|
+
tasks.forEach((task) => validateTaskParams(task.params));
|
|
235
|
+
|
|
236
|
+
const args = [
|
|
237
|
+
'agentic-flow',
|
|
238
|
+
'swarm',
|
|
239
|
+
'--tasks',
|
|
240
|
+
JSON.stringify(tasks),
|
|
241
|
+
'--strategy',
|
|
242
|
+
this.config.strategy!,
|
|
243
|
+
'--num-agents',
|
|
244
|
+
this.config.numAgents!.toString(),
|
|
245
|
+
'--format',
|
|
246
|
+
'json',
|
|
247
|
+
];
|
|
248
|
+
|
|
249
|
+
if (this.config.reasoningEnabled) args.push('--enable-reasoning');
|
|
250
|
+
|
|
251
|
+
const { stdout, code } = await spawnAsync('npx', args, { timeout: 60000 });
|
|
252
|
+
|
|
253
|
+
if (code !== 0) {
|
|
254
|
+
throw new Error(`Swarm execution failed with code ${code}`);
|
|
255
|
+
}
|
|
183
256
|
|
|
184
257
|
const lines = stdout.trim().split('\n');
|
|
185
|
-
const jsonLine = lines.find(line => line.startsWith('[') || line.startsWith('{'));
|
|
258
|
+
const jsonLine = lines.find((line) => line.startsWith('[') || line.startsWith('{'));
|
|
186
259
|
|
|
187
260
|
if (jsonLine) {
|
|
188
261
|
const data = JSON.parse(jsonLine);
|
|
@@ -206,8 +279,7 @@ export class FlowOrchestrator {
|
|
|
206
279
|
} catch (error: any) {
|
|
207
280
|
console.error(`❌ Swarm execution failed: ${error.message}`);
|
|
208
281
|
|
|
209
|
-
|
|
210
|
-
return tasks.map(task => ({
|
|
282
|
+
return tasks.map((task) => ({
|
|
211
283
|
taskId: task.id,
|
|
212
284
|
success: false,
|
|
213
285
|
result: { error: error.message },
|
|
@@ -218,9 +290,6 @@ export class FlowOrchestrator {
|
|
|
218
290
|
}
|
|
219
291
|
}
|
|
220
292
|
|
|
221
|
-
/**
|
|
222
|
-
* Coordinate multiple robots using agentic-flow
|
|
223
|
-
*/
|
|
224
293
|
async coordinateRobots(
|
|
225
294
|
robots: string[],
|
|
226
295
|
mission: {
|
|
@@ -234,22 +303,38 @@ export class FlowOrchestrator {
|
|
|
234
303
|
executionPlan: any;
|
|
235
304
|
estimatedTime: number;
|
|
236
305
|
}> {
|
|
306
|
+
if (!this.initialized) {
|
|
307
|
+
throw new Error('FlowOrchestrator not initialized. Call initialize() first.');
|
|
308
|
+
}
|
|
309
|
+
|
|
237
310
|
const startTime = Date.now();
|
|
238
311
|
|
|
239
312
|
try {
|
|
240
|
-
const
|
|
241
|
-
'
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
313
|
+
const args = [
|
|
314
|
+
'agentic-flow',
|
|
315
|
+
'coordinate',
|
|
316
|
+
'--robots',
|
|
317
|
+
JSON.stringify(robots),
|
|
318
|
+
'--mission-type',
|
|
319
|
+
mission.type,
|
|
320
|
+
'--objectives',
|
|
321
|
+
JSON.stringify(mission.objectives),
|
|
322
|
+
'--format',
|
|
323
|
+
'json',
|
|
324
|
+
];
|
|
325
|
+
|
|
326
|
+
if (mission.constraints) {
|
|
327
|
+
args.push('--constraints', JSON.stringify(mission.constraints));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const { stdout, code } = await spawnAsync('npx', args, { timeout: 30000 });
|
|
248
331
|
|
|
249
|
-
|
|
332
|
+
if (code !== 0) {
|
|
333
|
+
throw new Error(`Coordination failed with code ${code}`);
|
|
334
|
+
}
|
|
250
335
|
|
|
251
336
|
const lines = stdout.trim().split('\n');
|
|
252
|
-
const jsonLine = lines.find(line => line.startsWith('{'));
|
|
337
|
+
const jsonLine = lines.find((line) => line.startsWith('{'));
|
|
253
338
|
|
|
254
339
|
if (jsonLine) {
|
|
255
340
|
const data = JSON.parse(jsonLine);
|
|
@@ -267,20 +352,10 @@ export class FlowOrchestrator {
|
|
|
267
352
|
|
|
268
353
|
throw new Error('No valid JSON response from coordination');
|
|
269
354
|
} catch (error: any) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
return {
|
|
273
|
-
success: false,
|
|
274
|
-
assignments: {},
|
|
275
|
-
executionPlan: {},
|
|
276
|
-
estimatedTime: 0,
|
|
277
|
-
};
|
|
355
|
+
throw new Error(`Robot coordination failed: ${error.message}`);
|
|
278
356
|
}
|
|
279
357
|
}
|
|
280
358
|
|
|
281
|
-
/**
|
|
282
|
-
* Use ReasoningBank for complex decision making
|
|
283
|
-
*/
|
|
284
359
|
async reasonAboutTask(
|
|
285
360
|
context: string,
|
|
286
361
|
options: {
|
|
@@ -294,29 +369,29 @@ export class FlowOrchestrator {
|
|
|
294
369
|
confidence: number;
|
|
295
370
|
alternatives: string[];
|
|
296
371
|
}> {
|
|
297
|
-
|
|
372
|
+
if (!this.initialized) {
|
|
373
|
+
throw new Error('FlowOrchestrator not initialized. Call initialize() first.');
|
|
374
|
+
}
|
|
298
375
|
|
|
299
376
|
try {
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
options.explainReasoning ? '--explain-reasoning' : '',
|
|
306
|
-
'--format json',
|
|
307
|
-
].filter(Boolean).join(' ');
|
|
377
|
+
const args = ['agentic-flow', 'reason', '--context', context, '--format', 'json'];
|
|
378
|
+
|
|
379
|
+
if (options.useMemory) args.push('--use-memory');
|
|
380
|
+
if (options.synthesizeStrategy) args.push('--synthesize-strategy');
|
|
381
|
+
if (options.explainReasoning) args.push('--explain-reasoning');
|
|
308
382
|
|
|
309
|
-
const { stdout } = await
|
|
383
|
+
const { stdout, code } = await spawnAsync('npx', args, { timeout: 15000 });
|
|
384
|
+
|
|
385
|
+
if (code !== 0) {
|
|
386
|
+
throw new Error(`Reasoning failed with code ${code}`);
|
|
387
|
+
}
|
|
310
388
|
|
|
311
389
|
const lines = stdout.trim().split('\n');
|
|
312
|
-
const jsonLine = lines.find(line => line.startsWith('{'));
|
|
390
|
+
const jsonLine = lines.find((line) => line.startsWith('{'));
|
|
313
391
|
|
|
314
392
|
if (jsonLine) {
|
|
315
393
|
const data = JSON.parse(jsonLine);
|
|
316
394
|
|
|
317
|
-
const reasonTime = Date.now() - startTime;
|
|
318
|
-
console.error(`🧠 Reasoning completed in ${reasonTime}ms`);
|
|
319
|
-
|
|
320
395
|
return {
|
|
321
396
|
decision: data.decision || '',
|
|
322
397
|
reasoning: data.reasoning || '',
|
|
@@ -327,26 +402,25 @@ export class FlowOrchestrator {
|
|
|
327
402
|
|
|
328
403
|
throw new Error('No valid JSON response from reasoning');
|
|
329
404
|
} catch (error: any) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
return {
|
|
333
|
-
decision: '',
|
|
334
|
-
reasoning: error.message,
|
|
335
|
-
confidence: 0,
|
|
336
|
-
alternatives: [],
|
|
337
|
-
};
|
|
405
|
+
throw new Error(`Reasoning failed: ${error.message}`);
|
|
338
406
|
}
|
|
339
407
|
}
|
|
340
408
|
|
|
341
|
-
/**
|
|
342
|
-
* Get available MCP tools from agentic-flow
|
|
343
|
-
*/
|
|
344
409
|
async getAvailableTools(): Promise<string[]> {
|
|
345
410
|
try {
|
|
346
|
-
const { stdout } = await
|
|
411
|
+
const { stdout, code } = await spawnAsync('npx', [
|
|
412
|
+
'agentic-flow',
|
|
413
|
+
'list-tools',
|
|
414
|
+
'--format',
|
|
415
|
+
'json',
|
|
416
|
+
]);
|
|
417
|
+
|
|
418
|
+
if (code !== 0) {
|
|
419
|
+
throw new Error('Failed to list tools');
|
|
420
|
+
}
|
|
347
421
|
|
|
348
422
|
const lines = stdout.trim().split('\n');
|
|
349
|
-
const jsonLine = lines.find(line => line.startsWith('['));
|
|
423
|
+
const jsonLine = lines.find((line) => line.startsWith('['));
|
|
350
424
|
|
|
351
425
|
if (jsonLine) {
|
|
352
426
|
const tools = JSON.parse(jsonLine);
|
|
@@ -356,38 +430,27 @@ export class FlowOrchestrator {
|
|
|
356
430
|
|
|
357
431
|
return [];
|
|
358
432
|
} catch (error: any) {
|
|
359
|
-
|
|
360
|
-
return [];
|
|
433
|
+
throw new Error(`Failed to list tools: ${error.message}`);
|
|
361
434
|
}
|
|
362
435
|
}
|
|
363
436
|
|
|
364
|
-
/**
|
|
365
|
-
* Get metrics and statistics
|
|
366
|
-
*/
|
|
367
437
|
getMetrics(): OrchestrationMetrics {
|
|
368
438
|
return {
|
|
369
439
|
...this.metrics,
|
|
370
|
-
avgExecutionTime:
|
|
371
|
-
? this.metrics.avgExecutionTime / this.metrics.totalTasks
|
|
372
|
-
: 0,
|
|
440
|
+
avgExecutionTime:
|
|
441
|
+
this.metrics.totalTasks > 0 ? this.metrics.avgExecutionTime / this.metrics.totalTasks : 0,
|
|
373
442
|
};
|
|
374
443
|
}
|
|
375
444
|
|
|
376
|
-
/**
|
|
377
|
-
* Update internal metrics
|
|
378
|
-
*/
|
|
379
445
|
private updateMetrics(data: any, executionTime: number): void {
|
|
380
|
-
// Update execution time
|
|
381
446
|
this.metrics.avgExecutionTime += executionTime;
|
|
382
447
|
|
|
383
|
-
// Track agent utilization
|
|
384
448
|
if (data.agents_used) {
|
|
385
449
|
for (const agent of data.agents_used) {
|
|
386
450
|
this.metrics.agentUtilization[agent] = (this.metrics.agentUtilization[agent] || 0) + 1;
|
|
387
451
|
}
|
|
388
452
|
}
|
|
389
453
|
|
|
390
|
-
// Track tool usage
|
|
391
454
|
if (data.tools_used) {
|
|
392
455
|
for (const tool of data.tools_used) {
|
|
393
456
|
this.metrics.toolUsage[tool] = (this.metrics.toolUsage[tool] || 0) + 1;
|
|
@@ -395,9 +458,6 @@ export class FlowOrchestrator {
|
|
|
395
458
|
}
|
|
396
459
|
}
|
|
397
460
|
|
|
398
|
-
/**
|
|
399
|
-
* Reset metrics
|
|
400
|
-
*/
|
|
401
461
|
resetMetrics(): void {
|
|
402
462
|
this.metrics = {
|
|
403
463
|
totalTasks: 0,
|
package/src/hybrid-memory.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type { Episode, Memory } from './memory.js';
|
|
|
13
13
|
const execAsync = promisify(exec);
|
|
14
14
|
|
|
15
15
|
export class HybridAgentDBMemory {
|
|
16
|
-
private db
|
|
16
|
+
private db!: Database.Database;
|
|
17
17
|
private dbPath: string;
|
|
18
18
|
private initialized: boolean = false;
|
|
19
19
|
private performanceMetrics: {
|
|
@@ -363,7 +363,7 @@ export class HybridAgentDBMemory {
|
|
|
363
363
|
COUNT(DISTINCT task_name) as unique_tasks,
|
|
364
364
|
AVG(confidence) as avg_confidence
|
|
365
365
|
FROM reflexion_episodes
|
|
366
|
-
`).get();
|
|
366
|
+
`).get() || {};
|
|
367
367
|
|
|
368
368
|
return {
|
|
369
369
|
...stats,
|