@ruvector/edge-net 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -0
- package/agents.js +965 -0
- package/package.json +27 -3
- package/real-agents.js +706 -0
- package/sync.js +799 -0
package/agents.js
ADDED
|
@@ -0,0 +1,965 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Edge-Net Agent System
|
|
4
|
+
*
|
|
5
|
+
* Distributed AI agent execution across the Edge-Net collective.
|
|
6
|
+
* Spawn agents, create worker pools, and orchestrate multi-agent workflows.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { EventEmitter } from 'events';
|
|
10
|
+
import { randomBytes, createHash } from 'crypto';
|
|
11
|
+
|
|
12
|
+
// Agent types and their capabilities
|
|
13
|
+
export const AGENT_TYPES = {
|
|
14
|
+
researcher: {
|
|
15
|
+
name: 'Researcher',
|
|
16
|
+
capabilities: ['search', 'analyze', 'summarize', 'extract'],
|
|
17
|
+
baseRuv: 10,
|
|
18
|
+
description: 'Analyzes and researches information',
|
|
19
|
+
},
|
|
20
|
+
coder: {
|
|
21
|
+
name: 'Coder',
|
|
22
|
+
capabilities: ['code', 'refactor', 'debug', 'test'],
|
|
23
|
+
baseRuv: 15,
|
|
24
|
+
description: 'Writes and improves code',
|
|
25
|
+
},
|
|
26
|
+
reviewer: {
|
|
27
|
+
name: 'Reviewer',
|
|
28
|
+
capabilities: ['review', 'audit', 'validate', 'suggest'],
|
|
29
|
+
baseRuv: 12,
|
|
30
|
+
description: 'Reviews code and provides feedback',
|
|
31
|
+
},
|
|
32
|
+
tester: {
|
|
33
|
+
name: 'Tester',
|
|
34
|
+
capabilities: ['test', 'benchmark', 'validate', 'report'],
|
|
35
|
+
baseRuv: 10,
|
|
36
|
+
description: 'Tests and validates implementations',
|
|
37
|
+
},
|
|
38
|
+
analyst: {
|
|
39
|
+
name: 'Analyst',
|
|
40
|
+
capabilities: ['analyze', 'metrics', 'report', 'visualize'],
|
|
41
|
+
baseRuv: 8,
|
|
42
|
+
description: 'Analyzes data and generates reports',
|
|
43
|
+
},
|
|
44
|
+
optimizer: {
|
|
45
|
+
name: 'Optimizer',
|
|
46
|
+
capabilities: ['optimize', 'profile', 'benchmark', 'improve'],
|
|
47
|
+
baseRuv: 15,
|
|
48
|
+
description: 'Optimizes performance and efficiency',
|
|
49
|
+
},
|
|
50
|
+
coordinator: {
|
|
51
|
+
name: 'Coordinator',
|
|
52
|
+
capabilities: ['orchestrate', 'route', 'schedule', 'monitor'],
|
|
53
|
+
baseRuv: 20,
|
|
54
|
+
description: 'Coordinates multi-agent workflows',
|
|
55
|
+
},
|
|
56
|
+
embedder: {
|
|
57
|
+
name: 'Embedder',
|
|
58
|
+
capabilities: ['embed', 'vectorize', 'similarity', 'search'],
|
|
59
|
+
baseRuv: 5,
|
|
60
|
+
description: 'Generates embeddings and vector operations',
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Task status enum
|
|
65
|
+
export const TaskStatus = {
|
|
66
|
+
PENDING: 'pending',
|
|
67
|
+
QUEUED: 'queued',
|
|
68
|
+
ASSIGNED: 'assigned',
|
|
69
|
+
RUNNING: 'running',
|
|
70
|
+
COMPLETED: 'completed',
|
|
71
|
+
FAILED: 'failed',
|
|
72
|
+
CANCELLED: 'cancelled',
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Distributed Agent
|
|
77
|
+
*
|
|
78
|
+
* Represents an AI agent running on the Edge-Net network.
|
|
79
|
+
*/
|
|
80
|
+
export class DistributedAgent extends EventEmitter {
|
|
81
|
+
constructor(options) {
|
|
82
|
+
super();
|
|
83
|
+
this.id = `agent-${randomBytes(8).toString('hex')}`;
|
|
84
|
+
this.type = options.type || 'researcher';
|
|
85
|
+
this.task = options.task;
|
|
86
|
+
this.config = AGENT_TYPES[this.type] || AGENT_TYPES.researcher;
|
|
87
|
+
this.maxRuv = options.maxRuv || this.config.baseRuv;
|
|
88
|
+
this.priority = options.priority || 'medium';
|
|
89
|
+
this.timeout = options.timeout || 300000; // 5 min default
|
|
90
|
+
|
|
91
|
+
this.status = TaskStatus.PENDING;
|
|
92
|
+
this.assignedNode = null;
|
|
93
|
+
this.progress = 0;
|
|
94
|
+
this.result = null;
|
|
95
|
+
this.error = null;
|
|
96
|
+
this.startTime = null;
|
|
97
|
+
this.endTime = null;
|
|
98
|
+
this.ruvSpent = 0;
|
|
99
|
+
|
|
100
|
+
this.subtasks = [];
|
|
101
|
+
this.logs = [];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get agent info
|
|
106
|
+
*/
|
|
107
|
+
getInfo() {
|
|
108
|
+
return {
|
|
109
|
+
id: this.id,
|
|
110
|
+
type: this.type,
|
|
111
|
+
task: this.task,
|
|
112
|
+
status: this.status,
|
|
113
|
+
progress: this.progress,
|
|
114
|
+
assignedNode: this.assignedNode,
|
|
115
|
+
maxRuv: this.maxRuv,
|
|
116
|
+
ruvSpent: this.ruvSpent,
|
|
117
|
+
startTime: this.startTime,
|
|
118
|
+
endTime: this.endTime,
|
|
119
|
+
duration: this.endTime && this.startTime
|
|
120
|
+
? this.endTime - this.startTime
|
|
121
|
+
: null,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Update agent progress
|
|
127
|
+
*/
|
|
128
|
+
updateProgress(progress, message) {
|
|
129
|
+
this.progress = Math.min(100, Math.max(0, progress));
|
|
130
|
+
this.log(`Progress: ${this.progress}% - ${message}`);
|
|
131
|
+
this.emit('progress', { progress: this.progress, message });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Log message
|
|
136
|
+
*/
|
|
137
|
+
log(message) {
|
|
138
|
+
const entry = {
|
|
139
|
+
timestamp: Date.now(),
|
|
140
|
+
message,
|
|
141
|
+
};
|
|
142
|
+
this.logs.push(entry);
|
|
143
|
+
this.emit('log', entry);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Mark as completed
|
|
148
|
+
*/
|
|
149
|
+
complete(result) {
|
|
150
|
+
this.status = TaskStatus.COMPLETED;
|
|
151
|
+
this.result = result;
|
|
152
|
+
this.progress = 100;
|
|
153
|
+
this.endTime = Date.now();
|
|
154
|
+
this.log('Agent completed successfully');
|
|
155
|
+
this.emit('complete', result);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Mark as failed
|
|
160
|
+
*/
|
|
161
|
+
fail(error) {
|
|
162
|
+
this.status = TaskStatus.FAILED;
|
|
163
|
+
this.error = error;
|
|
164
|
+
this.endTime = Date.now();
|
|
165
|
+
this.log(`Agent failed: ${error}`);
|
|
166
|
+
this.emit('error', error);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Cancel the agent
|
|
171
|
+
*/
|
|
172
|
+
cancel() {
|
|
173
|
+
this.status = TaskStatus.CANCELLED;
|
|
174
|
+
this.endTime = Date.now();
|
|
175
|
+
this.log('Agent cancelled');
|
|
176
|
+
this.emit('cancelled');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Agent Spawner
|
|
182
|
+
*
|
|
183
|
+
* Spawns and manages distributed agents across the Edge-Net network.
|
|
184
|
+
*/
|
|
185
|
+
export class AgentSpawner extends EventEmitter {
|
|
186
|
+
constructor(networkManager, options = {}) {
|
|
187
|
+
super();
|
|
188
|
+
this.network = networkManager;
|
|
189
|
+
this.agents = new Map();
|
|
190
|
+
this.pendingQueue = [];
|
|
191
|
+
this.maxConcurrent = options.maxConcurrent || 10;
|
|
192
|
+
this.defaultTimeout = options.defaultTimeout || 300000;
|
|
193
|
+
|
|
194
|
+
// Agent routing table (learned from outcomes)
|
|
195
|
+
this.routingTable = new Map();
|
|
196
|
+
|
|
197
|
+
// Stats
|
|
198
|
+
this.stats = {
|
|
199
|
+
totalSpawned: 0,
|
|
200
|
+
completed: 0,
|
|
201
|
+
failed: 0,
|
|
202
|
+
totalRuvSpent: 0,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Spawn a new agent on the network
|
|
208
|
+
*/
|
|
209
|
+
async spawn(options) {
|
|
210
|
+
const agent = new DistributedAgent({
|
|
211
|
+
...options,
|
|
212
|
+
timeout: options.timeout || this.defaultTimeout,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
this.agents.set(agent.id, agent);
|
|
216
|
+
this.stats.totalSpawned++;
|
|
217
|
+
|
|
218
|
+
agent.log(`Agent spawned: ${agent.type} - ${agent.task}`);
|
|
219
|
+
agent.status = TaskStatus.QUEUED;
|
|
220
|
+
|
|
221
|
+
// Find best node for this agent type
|
|
222
|
+
const targetNode = await this.findBestNode(agent);
|
|
223
|
+
|
|
224
|
+
if (targetNode) {
|
|
225
|
+
await this.assignToNode(agent, targetNode);
|
|
226
|
+
} else {
|
|
227
|
+
// Queue for later assignment
|
|
228
|
+
this.pendingQueue.push(agent);
|
|
229
|
+
agent.log('Queued - waiting for available node');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
this.emit('agent-spawned', agent);
|
|
233
|
+
return agent;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Find the best node for an agent based on capabilities and load
|
|
238
|
+
*/
|
|
239
|
+
async findBestNode(agent) {
|
|
240
|
+
if (!this.network) return null;
|
|
241
|
+
|
|
242
|
+
const peers = this.network.getPeerList ?
|
|
243
|
+
this.network.getPeerList() :
|
|
244
|
+
Array.from(this.network.peers?.values() || []);
|
|
245
|
+
|
|
246
|
+
if (peers.length === 0) return null;
|
|
247
|
+
|
|
248
|
+
// Score each peer based on:
|
|
249
|
+
// 1. Capability match
|
|
250
|
+
// 2. Current load
|
|
251
|
+
// 3. Historical performance
|
|
252
|
+
// 4. Latency
|
|
253
|
+
const scoredPeers = peers.map(peer => {
|
|
254
|
+
let score = 50; // Base score
|
|
255
|
+
|
|
256
|
+
// Check capabilities
|
|
257
|
+
const peerCaps = peer.capabilities || [];
|
|
258
|
+
const requiredCaps = agent.config.capabilities;
|
|
259
|
+
const capMatch = requiredCaps.filter(c => peerCaps.includes(c)).length;
|
|
260
|
+
score += capMatch * 10;
|
|
261
|
+
|
|
262
|
+
// Check load (lower is better)
|
|
263
|
+
const load = peer.load || 0;
|
|
264
|
+
score -= load * 20;
|
|
265
|
+
|
|
266
|
+
// Check historical performance
|
|
267
|
+
const history = this.routingTable.get(`${peer.piKey || peer.id}-${agent.type}`);
|
|
268
|
+
if (history) {
|
|
269
|
+
score += history.successRate * 30;
|
|
270
|
+
score -= history.avgLatency / 1000; // Penalize high latency
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return { peer, score };
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// Sort by score (highest first)
|
|
277
|
+
scoredPeers.sort((a, b) => b.score - a.score);
|
|
278
|
+
|
|
279
|
+
return scoredPeers[0]?.peer || null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Assign agent to a specific node
|
|
284
|
+
*/
|
|
285
|
+
async assignToNode(agent, node) {
|
|
286
|
+
agent.status = TaskStatus.ASSIGNED;
|
|
287
|
+
agent.assignedNode = node.piKey || node.id;
|
|
288
|
+
agent.startTime = Date.now();
|
|
289
|
+
agent.log(`Assigned to node: ${agent.assignedNode.slice(0, 12)}...`);
|
|
290
|
+
|
|
291
|
+
// Send task to node via network
|
|
292
|
+
if (this.network?.sendToPeer) {
|
|
293
|
+
await this.network.sendToPeer(agent.assignedNode, {
|
|
294
|
+
type: 'agent_task',
|
|
295
|
+
agentId: agent.id,
|
|
296
|
+
agentType: agent.type,
|
|
297
|
+
task: agent.task,
|
|
298
|
+
maxRuv: agent.maxRuv,
|
|
299
|
+
timeout: agent.timeout,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
agent.status = TaskStatus.RUNNING;
|
|
304
|
+
this.emit('agent-assigned', { agent, node });
|
|
305
|
+
|
|
306
|
+
// Set timeout
|
|
307
|
+
setTimeout(() => {
|
|
308
|
+
if (agent.status === TaskStatus.RUNNING) {
|
|
309
|
+
agent.fail('Timeout exceeded');
|
|
310
|
+
}
|
|
311
|
+
}, agent.timeout);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Handle task result from network
|
|
316
|
+
*/
|
|
317
|
+
handleResult(agentId, result) {
|
|
318
|
+
const agent = this.agents.get(agentId);
|
|
319
|
+
if (!agent) return;
|
|
320
|
+
|
|
321
|
+
if (result.success) {
|
|
322
|
+
agent.complete(result.data);
|
|
323
|
+
this.stats.completed++;
|
|
324
|
+
this.updateRoutingTable(agent, true, result.latency);
|
|
325
|
+
} else {
|
|
326
|
+
agent.fail(result.error);
|
|
327
|
+
this.stats.failed++;
|
|
328
|
+
this.updateRoutingTable(agent, false, result.latency);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
agent.ruvSpent = result.ruvSpent || agent.config.baseRuv;
|
|
332
|
+
this.stats.totalRuvSpent += agent.ruvSpent;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Update routing table with outcome
|
|
337
|
+
*/
|
|
338
|
+
updateRoutingTable(agent, success, latency) {
|
|
339
|
+
const key = `${agent.assignedNode}-${agent.type}`;
|
|
340
|
+
const existing = this.routingTable.get(key) || {
|
|
341
|
+
attempts: 0,
|
|
342
|
+
successes: 0,
|
|
343
|
+
totalLatency: 0,
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
existing.attempts++;
|
|
347
|
+
if (success) existing.successes++;
|
|
348
|
+
existing.totalLatency += latency || 0;
|
|
349
|
+
existing.successRate = existing.successes / existing.attempts;
|
|
350
|
+
existing.avgLatency = existing.totalLatency / existing.attempts;
|
|
351
|
+
|
|
352
|
+
this.routingTable.set(key, existing);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Get agent by ID
|
|
357
|
+
*/
|
|
358
|
+
getAgent(agentId) {
|
|
359
|
+
return this.agents.get(agentId);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* List all agents
|
|
364
|
+
*/
|
|
365
|
+
listAgents(filter = {}) {
|
|
366
|
+
let agents = Array.from(this.agents.values());
|
|
367
|
+
|
|
368
|
+
if (filter.status) {
|
|
369
|
+
agents = agents.filter(a => a.status === filter.status);
|
|
370
|
+
}
|
|
371
|
+
if (filter.type) {
|
|
372
|
+
agents = agents.filter(a => a.type === filter.type);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return agents.map(a => a.getInfo());
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Get spawner stats
|
|
380
|
+
*/
|
|
381
|
+
getStats() {
|
|
382
|
+
return {
|
|
383
|
+
...this.stats,
|
|
384
|
+
activeAgents: Array.from(this.agents.values())
|
|
385
|
+
.filter(a => a.status === TaskStatus.RUNNING).length,
|
|
386
|
+
queuedAgents: this.pendingQueue.length,
|
|
387
|
+
successRate: this.stats.completed /
|
|
388
|
+
(this.stats.completed + this.stats.failed) || 0,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Worker Pool
|
|
395
|
+
*
|
|
396
|
+
* Manages a pool of distributed workers for parallel task execution.
|
|
397
|
+
*/
|
|
398
|
+
export class WorkerPool extends EventEmitter {
|
|
399
|
+
constructor(networkManager, options = {}) {
|
|
400
|
+
super();
|
|
401
|
+
this.id = `pool-${randomBytes(6).toString('hex')}`;
|
|
402
|
+
this.network = networkManager;
|
|
403
|
+
this.size = options.size || 5;
|
|
404
|
+
this.capabilities = options.capabilities || ['compute', 'embed'];
|
|
405
|
+
this.maxTasksPerWorker = options.maxTasksPerWorker || 10;
|
|
406
|
+
|
|
407
|
+
this.workers = new Map();
|
|
408
|
+
this.taskQueue = [];
|
|
409
|
+
this.activeTasks = new Map();
|
|
410
|
+
this.results = new Map();
|
|
411
|
+
|
|
412
|
+
this.status = 'initializing';
|
|
413
|
+
this.stats = {
|
|
414
|
+
tasksCompleted: 0,
|
|
415
|
+
tasksFailed: 0,
|
|
416
|
+
totalProcessingTime: 0,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Initialize the worker pool
|
|
422
|
+
*/
|
|
423
|
+
async initialize() {
|
|
424
|
+
this.status = 'recruiting';
|
|
425
|
+
this.emit('status', 'Recruiting workers...');
|
|
426
|
+
|
|
427
|
+
// Find available workers from network
|
|
428
|
+
const peers = this.network?.getPeerList?.() ||
|
|
429
|
+
Array.from(this.network?.peers?.values() || []);
|
|
430
|
+
|
|
431
|
+
// Filter peers by capabilities
|
|
432
|
+
const eligiblePeers = peers.filter(peer => {
|
|
433
|
+
const peerCaps = peer.capabilities || [];
|
|
434
|
+
return this.capabilities.some(c => peerCaps.includes(c));
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// Recruit up to pool size
|
|
438
|
+
const recruited = eligiblePeers.slice(0, this.size);
|
|
439
|
+
|
|
440
|
+
for (const peer of recruited) {
|
|
441
|
+
this.workers.set(peer.piKey || peer.id, {
|
|
442
|
+
id: peer.piKey || peer.id,
|
|
443
|
+
peer,
|
|
444
|
+
status: 'idle',
|
|
445
|
+
currentTasks: 0,
|
|
446
|
+
completedTasks: 0,
|
|
447
|
+
lastSeen: Date.now(),
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// If not enough real workers, create virtual workers for local execution
|
|
452
|
+
while (this.workers.size < this.size) {
|
|
453
|
+
const virtualId = `virtual-${randomBytes(4).toString('hex')}`;
|
|
454
|
+
this.workers.set(virtualId, {
|
|
455
|
+
id: virtualId,
|
|
456
|
+
peer: null,
|
|
457
|
+
status: 'idle',
|
|
458
|
+
currentTasks: 0,
|
|
459
|
+
completedTasks: 0,
|
|
460
|
+
isVirtual: true,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
this.status = 'ready';
|
|
465
|
+
this.emit('ready', {
|
|
466
|
+
poolId: this.id,
|
|
467
|
+
workers: this.workers.size,
|
|
468
|
+
realWorkers: Array.from(this.workers.values())
|
|
469
|
+
.filter(w => !w.isVirtual).length,
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
return this;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Execute tasks in parallel across workers
|
|
477
|
+
*/
|
|
478
|
+
async execute(options) {
|
|
479
|
+
const {
|
|
480
|
+
task,
|
|
481
|
+
data,
|
|
482
|
+
strategy = 'parallel',
|
|
483
|
+
chunkSize = null,
|
|
484
|
+
} = options;
|
|
485
|
+
|
|
486
|
+
const batchId = `batch-${randomBytes(6).toString('hex')}`;
|
|
487
|
+
const startTime = Date.now();
|
|
488
|
+
|
|
489
|
+
// Split data into chunks for workers
|
|
490
|
+
let chunks;
|
|
491
|
+
if (Array.isArray(data)) {
|
|
492
|
+
const size = chunkSize || Math.ceil(data.length / this.workers.size);
|
|
493
|
+
chunks = [];
|
|
494
|
+
for (let i = 0; i < data.length; i += size) {
|
|
495
|
+
chunks.push(data.slice(i, i + size));
|
|
496
|
+
}
|
|
497
|
+
} else {
|
|
498
|
+
chunks = [data];
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
this.emit('batch-start', { batchId, chunks: chunks.length });
|
|
502
|
+
|
|
503
|
+
// Assign chunks to workers
|
|
504
|
+
const promises = chunks.map((chunk, index) =>
|
|
505
|
+
this.assignTask({
|
|
506
|
+
batchId,
|
|
507
|
+
index,
|
|
508
|
+
task,
|
|
509
|
+
data: chunk,
|
|
510
|
+
})
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
// Wait for all or handle based on strategy
|
|
514
|
+
let results;
|
|
515
|
+
if (strategy === 'parallel') {
|
|
516
|
+
results = await Promise.all(promises);
|
|
517
|
+
} else if (strategy === 'race') {
|
|
518
|
+
results = [await Promise.race(promises)];
|
|
519
|
+
} else {
|
|
520
|
+
// Sequential
|
|
521
|
+
results = [];
|
|
522
|
+
for (const promise of promises) {
|
|
523
|
+
results.push(await promise);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const endTime = Date.now();
|
|
528
|
+
this.stats.totalProcessingTime += endTime - startTime;
|
|
529
|
+
|
|
530
|
+
this.emit('batch-complete', {
|
|
531
|
+
batchId,
|
|
532
|
+
duration: endTime - startTime,
|
|
533
|
+
results: results.length,
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
// Flatten results if array
|
|
537
|
+
return Array.isArray(data) ? results.flat() : results[0];
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Assign a single task to an available worker
|
|
542
|
+
*/
|
|
543
|
+
async assignTask(taskInfo) {
|
|
544
|
+
const taskId = `task-${randomBytes(6).toString('hex')}`;
|
|
545
|
+
|
|
546
|
+
// Find idle worker
|
|
547
|
+
const worker = this.findIdleWorker();
|
|
548
|
+
if (!worker) {
|
|
549
|
+
// Queue task
|
|
550
|
+
return new Promise((resolve, reject) => {
|
|
551
|
+
this.taskQueue.push({ taskInfo, resolve, reject });
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
worker.status = 'busy';
|
|
556
|
+
worker.currentTasks++;
|
|
557
|
+
|
|
558
|
+
this.activeTasks.set(taskId, {
|
|
559
|
+
...taskInfo,
|
|
560
|
+
workerId: worker.id,
|
|
561
|
+
startTime: Date.now(),
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
try {
|
|
565
|
+
// Execute on worker
|
|
566
|
+
const result = await this.executeOnWorker(worker, taskInfo);
|
|
567
|
+
|
|
568
|
+
worker.completedTasks++;
|
|
569
|
+
this.stats.tasksCompleted++;
|
|
570
|
+
this.results.set(taskId, result);
|
|
571
|
+
|
|
572
|
+
return result;
|
|
573
|
+
} catch (error) {
|
|
574
|
+
this.stats.tasksFailed++;
|
|
575
|
+
throw error;
|
|
576
|
+
} finally {
|
|
577
|
+
worker.currentTasks--;
|
|
578
|
+
if (worker.currentTasks === 0) {
|
|
579
|
+
worker.status = 'idle';
|
|
580
|
+
}
|
|
581
|
+
this.activeTasks.delete(taskId);
|
|
582
|
+
|
|
583
|
+
// Process queued task if any
|
|
584
|
+
if (this.taskQueue.length > 0) {
|
|
585
|
+
const { taskInfo, resolve, reject } = this.taskQueue.shift();
|
|
586
|
+
this.assignTask(taskInfo).then(resolve).catch(reject);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Find an idle worker
|
|
593
|
+
*/
|
|
594
|
+
findIdleWorker() {
|
|
595
|
+
for (const worker of this.workers.values()) {
|
|
596
|
+
if (worker.status === 'idle' ||
|
|
597
|
+
worker.currentTasks < this.maxTasksPerWorker) {
|
|
598
|
+
return worker;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return null;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Execute task on a specific worker
|
|
606
|
+
*/
|
|
607
|
+
async executeOnWorker(worker, taskInfo) {
|
|
608
|
+
if (worker.isVirtual) {
|
|
609
|
+
// Local execution for virtual workers
|
|
610
|
+
return this.executeLocally(taskInfo);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Send to remote worker via network
|
|
614
|
+
return new Promise((resolve, reject) => {
|
|
615
|
+
const timeout = setTimeout(() => {
|
|
616
|
+
reject(new Error('Worker timeout'));
|
|
617
|
+
}, 60000);
|
|
618
|
+
|
|
619
|
+
// Send task
|
|
620
|
+
if (this.network?.sendToPeer) {
|
|
621
|
+
this.network.sendToPeer(worker.id, {
|
|
622
|
+
type: 'worker_task',
|
|
623
|
+
poolId: this.id,
|
|
624
|
+
task: taskInfo.task,
|
|
625
|
+
data: taskInfo.data,
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Listen for result
|
|
630
|
+
const handler = (msg) => {
|
|
631
|
+
if (msg.poolId === this.id && msg.batchId === taskInfo.batchId) {
|
|
632
|
+
clearTimeout(timeout);
|
|
633
|
+
this.network?.off?.('worker_result', handler);
|
|
634
|
+
resolve(msg.result);
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
this.network?.on?.('worker_result', handler);
|
|
639
|
+
|
|
640
|
+
// Fallback to local if no response
|
|
641
|
+
setTimeout(() => {
|
|
642
|
+
clearTimeout(timeout);
|
|
643
|
+
this.executeLocally(taskInfo).then(resolve).catch(reject);
|
|
644
|
+
}, 5000);
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Execute task locally (for virtual workers or fallback)
|
|
650
|
+
*/
|
|
651
|
+
async executeLocally(taskInfo) {
|
|
652
|
+
const { task, data } = taskInfo;
|
|
653
|
+
|
|
654
|
+
// Simple local execution based on task type
|
|
655
|
+
switch (task) {
|
|
656
|
+
case 'embed':
|
|
657
|
+
// Simulate embedding
|
|
658
|
+
return Array.isArray(data)
|
|
659
|
+
? data.map(() => new Array(384).fill(0).map(() => Math.random()))
|
|
660
|
+
: new Array(384).fill(0).map(() => Math.random());
|
|
661
|
+
|
|
662
|
+
case 'process':
|
|
663
|
+
return Array.isArray(data)
|
|
664
|
+
? data.map(item => ({ processed: true, item }))
|
|
665
|
+
: { processed: true, data };
|
|
666
|
+
|
|
667
|
+
case 'analyze':
|
|
668
|
+
return {
|
|
669
|
+
analyzed: true,
|
|
670
|
+
itemCount: Array.isArray(data) ? data.length : 1,
|
|
671
|
+
timestamp: Date.now(),
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
default:
|
|
675
|
+
return { task, data, executed: true };
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Get pool status
|
|
681
|
+
*/
|
|
682
|
+
getStatus() {
|
|
683
|
+
const workers = Array.from(this.workers.values());
|
|
684
|
+
return {
|
|
685
|
+
poolId: this.id,
|
|
686
|
+
status: this.status,
|
|
687
|
+
totalWorkers: workers.length,
|
|
688
|
+
idleWorkers: workers.filter(w => w.status === 'idle').length,
|
|
689
|
+
busyWorkers: workers.filter(w => w.status === 'busy').length,
|
|
690
|
+
virtualWorkers: workers.filter(w => w.isVirtual).length,
|
|
691
|
+
queuedTasks: this.taskQueue.length,
|
|
692
|
+
activeTasks: this.activeTasks.size,
|
|
693
|
+
stats: this.stats,
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Shutdown the pool
|
|
699
|
+
*/
|
|
700
|
+
async shutdown() {
|
|
701
|
+
this.status = 'shutting_down';
|
|
702
|
+
|
|
703
|
+
// Wait for active tasks
|
|
704
|
+
while (this.activeTasks.size > 0) {
|
|
705
|
+
await new Promise(r => setTimeout(r, 100));
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Clear workers
|
|
709
|
+
this.workers.clear();
|
|
710
|
+
this.status = 'shutdown';
|
|
711
|
+
this.emit('shutdown');
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Task Orchestrator
|
|
717
|
+
*
|
|
718
|
+
* Orchestrates multi-agent workflows and complex task pipelines.
|
|
719
|
+
*/
|
|
720
|
+
export class TaskOrchestrator extends EventEmitter {
|
|
721
|
+
constructor(agentSpawner, workerPool, options = {}) {
|
|
722
|
+
super();
|
|
723
|
+
this.spawner = agentSpawner;
|
|
724
|
+
this.pool = workerPool;
|
|
725
|
+
this.workflows = new Map();
|
|
726
|
+
this.maxConcurrentWorkflows = options.maxConcurrentWorkflows || 5;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Create a workflow
|
|
731
|
+
*/
|
|
732
|
+
createWorkflow(name, steps) {
|
|
733
|
+
const workflow = {
|
|
734
|
+
id: `wf-${randomBytes(6).toString('hex')}`,
|
|
735
|
+
name,
|
|
736
|
+
steps,
|
|
737
|
+
status: 'created',
|
|
738
|
+
currentStep: 0,
|
|
739
|
+
results: [],
|
|
740
|
+
startTime: null,
|
|
741
|
+
endTime: null,
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
this.workflows.set(workflow.id, workflow);
|
|
745
|
+
return workflow;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Execute a workflow
|
|
750
|
+
*/
|
|
751
|
+
async executeWorkflow(workflowId, input = {}) {
|
|
752
|
+
const workflow = this.workflows.get(workflowId);
|
|
753
|
+
if (!workflow) throw new Error('Workflow not found');
|
|
754
|
+
|
|
755
|
+
workflow.status = 'running';
|
|
756
|
+
workflow.startTime = Date.now();
|
|
757
|
+
workflow.input = input;
|
|
758
|
+
|
|
759
|
+
this.emit('workflow-start', { workflowId, name: workflow.name });
|
|
760
|
+
|
|
761
|
+
try {
|
|
762
|
+
let context = { ...input };
|
|
763
|
+
|
|
764
|
+
for (let i = 0; i < workflow.steps.length; i++) {
|
|
765
|
+
workflow.currentStep = i;
|
|
766
|
+
const step = workflow.steps[i];
|
|
767
|
+
|
|
768
|
+
this.emit('step-start', {
|
|
769
|
+
workflowId,
|
|
770
|
+
step: i,
|
|
771
|
+
type: step.type,
|
|
772
|
+
name: step.name,
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
const result = await this.executeStep(step, context);
|
|
776
|
+
workflow.results.push(result);
|
|
777
|
+
|
|
778
|
+
// Pass result to next step
|
|
779
|
+
context = { ...context, [step.name || `step${i}`]: result };
|
|
780
|
+
|
|
781
|
+
this.emit('step-complete', {
|
|
782
|
+
workflowId,
|
|
783
|
+
step: i,
|
|
784
|
+
result,
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
workflow.status = 'completed';
|
|
789
|
+
workflow.endTime = Date.now();
|
|
790
|
+
|
|
791
|
+
this.emit('workflow-complete', {
|
|
792
|
+
workflowId,
|
|
793
|
+
duration: workflow.endTime - workflow.startTime,
|
|
794
|
+
results: workflow.results,
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
return {
|
|
798
|
+
success: true,
|
|
799
|
+
results: workflow.results,
|
|
800
|
+
context,
|
|
801
|
+
};
|
|
802
|
+
|
|
803
|
+
} catch (error) {
|
|
804
|
+
workflow.status = 'failed';
|
|
805
|
+
workflow.endTime = Date.now();
|
|
806
|
+
workflow.error = error.message;
|
|
807
|
+
|
|
808
|
+
this.emit('workflow-failed', { workflowId, error: error.message });
|
|
809
|
+
|
|
810
|
+
return {
|
|
811
|
+
success: false,
|
|
812
|
+
error: error.message,
|
|
813
|
+
failedStep: workflow.currentStep,
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* Execute a single workflow step
|
|
820
|
+
*/
|
|
821
|
+
async executeStep(step, context) {
|
|
822
|
+
switch (step.type) {
|
|
823
|
+
case 'agent':
|
|
824
|
+
return this.executeAgentStep(step, context);
|
|
825
|
+
|
|
826
|
+
case 'parallel':
|
|
827
|
+
return this.executeParallelStep(step, context);
|
|
828
|
+
|
|
829
|
+
case 'pool':
|
|
830
|
+
return this.executePoolStep(step, context);
|
|
831
|
+
|
|
832
|
+
case 'condition':
|
|
833
|
+
return this.executeConditionStep(step, context);
|
|
834
|
+
|
|
835
|
+
case 'transform':
|
|
836
|
+
return this.executeTransformStep(step, context);
|
|
837
|
+
|
|
838
|
+
default:
|
|
839
|
+
throw new Error(`Unknown step type: ${step.type}`);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Execute an agent step
|
|
845
|
+
*/
|
|
846
|
+
async executeAgentStep(step, context) {
|
|
847
|
+
const task = typeof step.task === 'function'
|
|
848
|
+
? step.task(context)
|
|
849
|
+
: step.task;
|
|
850
|
+
|
|
851
|
+
const agent = await this.spawner.spawn({
|
|
852
|
+
type: step.agentType || 'researcher',
|
|
853
|
+
task,
|
|
854
|
+
maxRuv: step.maxRuv,
|
|
855
|
+
priority: step.priority,
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
return new Promise((resolve, reject) => {
|
|
859
|
+
agent.on('complete', resolve);
|
|
860
|
+
agent.on('error', reject);
|
|
861
|
+
|
|
862
|
+
// Simulate completion for now
|
|
863
|
+
setTimeout(() => {
|
|
864
|
+
agent.complete({
|
|
865
|
+
task,
|
|
866
|
+
result: `Completed: ${task}`,
|
|
867
|
+
context,
|
|
868
|
+
});
|
|
869
|
+
}, 1000);
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* Execute parallel agents
|
|
875
|
+
*/
|
|
876
|
+
async executeParallelStep(step, context) {
|
|
877
|
+
const promises = step.agents.map(agentConfig =>
|
|
878
|
+
this.executeAgentStep(agentConfig, context)
|
|
879
|
+
);
|
|
880
|
+
|
|
881
|
+
return Promise.all(promises);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* Execute worker pool step
|
|
886
|
+
*/
|
|
887
|
+
async executePoolStep(step, context) {
|
|
888
|
+
const data = typeof step.data === 'function'
|
|
889
|
+
? step.data(context)
|
|
890
|
+
: step.data || context.data;
|
|
891
|
+
|
|
892
|
+
return this.pool.execute({
|
|
893
|
+
task: step.task,
|
|
894
|
+
data,
|
|
895
|
+
strategy: step.strategy || 'parallel',
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* Execute conditional step
|
|
901
|
+
*/
|
|
902
|
+
async executeConditionStep(step, context) {
|
|
903
|
+
const condition = typeof step.condition === 'function'
|
|
904
|
+
? step.condition(context)
|
|
905
|
+
: step.condition;
|
|
906
|
+
|
|
907
|
+
if (condition) {
|
|
908
|
+
return this.executeStep(step.then, context);
|
|
909
|
+
} else if (step.else) {
|
|
910
|
+
return this.executeStep(step.else, context);
|
|
911
|
+
}
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
/**
|
|
916
|
+
* Execute transform step
|
|
917
|
+
*/
|
|
918
|
+
async executeTransformStep(step, context) {
|
|
919
|
+
return step.transform(context);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* Get workflow status
|
|
924
|
+
*/
|
|
925
|
+
getWorkflowStatus(workflowId) {
|
|
926
|
+
const workflow = this.workflows.get(workflowId);
|
|
927
|
+
if (!workflow) return null;
|
|
928
|
+
|
|
929
|
+
return {
|
|
930
|
+
id: workflow.id,
|
|
931
|
+
name: workflow.name,
|
|
932
|
+
status: workflow.status,
|
|
933
|
+
currentStep: workflow.currentStep,
|
|
934
|
+
totalSteps: workflow.steps.length,
|
|
935
|
+
progress: (workflow.currentStep / workflow.steps.length) * 100,
|
|
936
|
+
startTime: workflow.startTime,
|
|
937
|
+
endTime: workflow.endTime,
|
|
938
|
+
duration: workflow.endTime && workflow.startTime
|
|
939
|
+
? workflow.endTime - workflow.startTime
|
|
940
|
+
: null,
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
/**
|
|
945
|
+
* List all workflows
|
|
946
|
+
*/
|
|
947
|
+
listWorkflows() {
|
|
948
|
+
return Array.from(this.workflows.values()).map(w => ({
|
|
949
|
+
id: w.id,
|
|
950
|
+
name: w.name,
|
|
951
|
+
status: w.status,
|
|
952
|
+
steps: w.steps.length,
|
|
953
|
+
}));
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// Export default instances
|
|
958
|
+
export default {
|
|
959
|
+
AGENT_TYPES,
|
|
960
|
+
TaskStatus,
|
|
961
|
+
DistributedAgent,
|
|
962
|
+
AgentSpawner,
|
|
963
|
+
WorkerPool,
|
|
964
|
+
TaskOrchestrator,
|
|
965
|
+
};
|