966 lines
27 KiB
JavaScript
966 lines
27 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Edge-Net Agent System
|
|
*
|
|
* Distributed AI agent execution across the Edge-Net collective.
|
|
* Spawn agents, create worker pools, and orchestrate multi-agent workflows.
|
|
*/
|
|
|
|
import { EventEmitter } from 'events';
|
|
import { randomBytes, createHash } from 'crypto';
|
|
|
|
// Agent types and their capabilities
|
|
export const AGENT_TYPES = {
|
|
researcher: {
|
|
name: 'Researcher',
|
|
capabilities: ['search', 'analyze', 'summarize', 'extract'],
|
|
baseRuv: 10,
|
|
description: 'Analyzes and researches information',
|
|
},
|
|
coder: {
|
|
name: 'Coder',
|
|
capabilities: ['code', 'refactor', 'debug', 'test'],
|
|
baseRuv: 15,
|
|
description: 'Writes and improves code',
|
|
},
|
|
reviewer: {
|
|
name: 'Reviewer',
|
|
capabilities: ['review', 'audit', 'validate', 'suggest'],
|
|
baseRuv: 12,
|
|
description: 'Reviews code and provides feedback',
|
|
},
|
|
tester: {
|
|
name: 'Tester',
|
|
capabilities: ['test', 'benchmark', 'validate', 'report'],
|
|
baseRuv: 10,
|
|
description: 'Tests and validates implementations',
|
|
},
|
|
analyst: {
|
|
name: 'Analyst',
|
|
capabilities: ['analyze', 'metrics', 'report', 'visualize'],
|
|
baseRuv: 8,
|
|
description: 'Analyzes data and generates reports',
|
|
},
|
|
optimizer: {
|
|
name: 'Optimizer',
|
|
capabilities: ['optimize', 'profile', 'benchmark', 'improve'],
|
|
baseRuv: 15,
|
|
description: 'Optimizes performance and efficiency',
|
|
},
|
|
coordinator: {
|
|
name: 'Coordinator',
|
|
capabilities: ['orchestrate', 'route', 'schedule', 'monitor'],
|
|
baseRuv: 20,
|
|
description: 'Coordinates multi-agent workflows',
|
|
},
|
|
embedder: {
|
|
name: 'Embedder',
|
|
capabilities: ['embed', 'vectorize', 'similarity', 'search'],
|
|
baseRuv: 5,
|
|
description: 'Generates embeddings and vector operations',
|
|
},
|
|
};
|
|
|
|
// Task status enum
|
|
export const TaskStatus = {
|
|
PENDING: 'pending',
|
|
QUEUED: 'queued',
|
|
ASSIGNED: 'assigned',
|
|
RUNNING: 'running',
|
|
COMPLETED: 'completed',
|
|
FAILED: 'failed',
|
|
CANCELLED: 'cancelled',
|
|
};
|
|
|
|
/**
|
|
* Distributed Agent
|
|
*
|
|
* Represents an AI agent running on the Edge-Net network.
|
|
*/
|
|
export class DistributedAgent extends EventEmitter {
|
|
constructor(options) {
|
|
super();
|
|
this.id = `agent-${randomBytes(8).toString('hex')}`;
|
|
this.type = options.type || 'researcher';
|
|
this.task = options.task;
|
|
this.config = AGENT_TYPES[this.type] || AGENT_TYPES.researcher;
|
|
this.maxRuv = options.maxRuv || this.config.baseRuv;
|
|
this.priority = options.priority || 'medium';
|
|
this.timeout = options.timeout || 300000; // 5 min default
|
|
|
|
this.status = TaskStatus.PENDING;
|
|
this.assignedNode = null;
|
|
this.progress = 0;
|
|
this.result = null;
|
|
this.error = null;
|
|
this.startTime = null;
|
|
this.endTime = null;
|
|
this.ruvSpent = 0;
|
|
|
|
this.subtasks = [];
|
|
this.logs = [];
|
|
}
|
|
|
|
/**
|
|
* Get agent info
|
|
*/
|
|
getInfo() {
|
|
return {
|
|
id: this.id,
|
|
type: this.type,
|
|
task: this.task,
|
|
status: this.status,
|
|
progress: this.progress,
|
|
assignedNode: this.assignedNode,
|
|
maxRuv: this.maxRuv,
|
|
ruvSpent: this.ruvSpent,
|
|
startTime: this.startTime,
|
|
endTime: this.endTime,
|
|
duration: this.endTime && this.startTime
|
|
? this.endTime - this.startTime
|
|
: null,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Update agent progress
|
|
*/
|
|
updateProgress(progress, message) {
|
|
this.progress = Math.min(100, Math.max(0, progress));
|
|
this.log(`Progress: ${this.progress}% - ${message}`);
|
|
this.emit('progress', { progress: this.progress, message });
|
|
}
|
|
|
|
/**
|
|
* Log message
|
|
*/
|
|
log(message) {
|
|
const entry = {
|
|
timestamp: Date.now(),
|
|
message,
|
|
};
|
|
this.logs.push(entry);
|
|
this.emit('log', entry);
|
|
}
|
|
|
|
/**
|
|
* Mark as completed
|
|
*/
|
|
complete(result) {
|
|
this.status = TaskStatus.COMPLETED;
|
|
this.result = result;
|
|
this.progress = 100;
|
|
this.endTime = Date.now();
|
|
this.log('Agent completed successfully');
|
|
this.emit('complete', result);
|
|
}
|
|
|
|
/**
|
|
* Mark as failed
|
|
*/
|
|
fail(error) {
|
|
this.status = TaskStatus.FAILED;
|
|
this.error = error;
|
|
this.endTime = Date.now();
|
|
this.log(`Agent failed: ${error}`);
|
|
this.emit('error', error);
|
|
}
|
|
|
|
/**
|
|
* Cancel the agent
|
|
*/
|
|
cancel() {
|
|
this.status = TaskStatus.CANCELLED;
|
|
this.endTime = Date.now();
|
|
this.log('Agent cancelled');
|
|
this.emit('cancelled');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Agent Spawner
|
|
*
|
|
* Spawns and manages distributed agents across the Edge-Net network.
|
|
*/
|
|
export class AgentSpawner extends EventEmitter {
|
|
constructor(networkManager, options = {}) {
|
|
super();
|
|
this.network = networkManager;
|
|
this.agents = new Map();
|
|
this.pendingQueue = [];
|
|
this.maxConcurrent = options.maxConcurrent || 10;
|
|
this.defaultTimeout = options.defaultTimeout || 300000;
|
|
|
|
// Agent routing table (learned from outcomes)
|
|
this.routingTable = new Map();
|
|
|
|
// Stats
|
|
this.stats = {
|
|
totalSpawned: 0,
|
|
completed: 0,
|
|
failed: 0,
|
|
totalRuvSpent: 0,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Spawn a new agent on the network
|
|
*/
|
|
async spawn(options) {
|
|
const agent = new DistributedAgent({
|
|
...options,
|
|
timeout: options.timeout || this.defaultTimeout,
|
|
});
|
|
|
|
this.agents.set(agent.id, agent);
|
|
this.stats.totalSpawned++;
|
|
|
|
agent.log(`Agent spawned: ${agent.type} - ${agent.task}`);
|
|
agent.status = TaskStatus.QUEUED;
|
|
|
|
// Find best node for this agent type
|
|
const targetNode = await this.findBestNode(agent);
|
|
|
|
if (targetNode) {
|
|
await this.assignToNode(agent, targetNode);
|
|
} else {
|
|
// Queue for later assignment
|
|
this.pendingQueue.push(agent);
|
|
agent.log('Queued - waiting for available node');
|
|
}
|
|
|
|
this.emit('agent-spawned', agent);
|
|
return agent;
|
|
}
|
|
|
|
/**
|
|
* Find the best node for an agent based on capabilities and load
|
|
*/
|
|
async findBestNode(agent) {
|
|
if (!this.network) return null;
|
|
|
|
const peers = this.network.getPeerList ?
|
|
this.network.getPeerList() :
|
|
Array.from(this.network.peers?.values() || []);
|
|
|
|
if (peers.length === 0) return null;
|
|
|
|
// Score each peer based on:
|
|
// 1. Capability match
|
|
// 2. Current load
|
|
// 3. Historical performance
|
|
// 4. Latency
|
|
const scoredPeers = peers.map(peer => {
|
|
let score = 50; // Base score
|
|
|
|
// Check capabilities
|
|
const peerCaps = peer.capabilities || [];
|
|
const requiredCaps = agent.config.capabilities;
|
|
const capMatch = requiredCaps.filter(c => peerCaps.includes(c)).length;
|
|
score += capMatch * 10;
|
|
|
|
// Check load (lower is better)
|
|
const load = peer.load || 0;
|
|
score -= load * 20;
|
|
|
|
// Check historical performance
|
|
const history = this.routingTable.get(`${peer.piKey || peer.id}-${agent.type}`);
|
|
if (history) {
|
|
score += history.successRate * 30;
|
|
score -= history.avgLatency / 1000; // Penalize high latency
|
|
}
|
|
|
|
return { peer, score };
|
|
});
|
|
|
|
// Sort by score (highest first)
|
|
scoredPeers.sort((a, b) => b.score - a.score);
|
|
|
|
return scoredPeers[0]?.peer || null;
|
|
}
|
|
|
|
/**
|
|
* Assign agent to a specific node
|
|
*/
|
|
async assignToNode(agent, node) {
|
|
agent.status = TaskStatus.ASSIGNED;
|
|
agent.assignedNode = node.piKey || node.id;
|
|
agent.startTime = Date.now();
|
|
agent.log(`Assigned to node: ${agent.assignedNode.slice(0, 12)}...`);
|
|
|
|
// Send task to node via network
|
|
if (this.network?.sendToPeer) {
|
|
await this.network.sendToPeer(agent.assignedNode, {
|
|
type: 'agent_task',
|
|
agentId: agent.id,
|
|
agentType: agent.type,
|
|
task: agent.task,
|
|
maxRuv: agent.maxRuv,
|
|
timeout: agent.timeout,
|
|
});
|
|
}
|
|
|
|
agent.status = TaskStatus.RUNNING;
|
|
this.emit('agent-assigned', { agent, node });
|
|
|
|
// Set timeout
|
|
setTimeout(() => {
|
|
if (agent.status === TaskStatus.RUNNING) {
|
|
agent.fail('Timeout exceeded');
|
|
}
|
|
}, agent.timeout);
|
|
}
|
|
|
|
/**
|
|
* Handle task result from network
|
|
*/
|
|
handleResult(agentId, result) {
|
|
const agent = this.agents.get(agentId);
|
|
if (!agent) return;
|
|
|
|
if (result.success) {
|
|
agent.complete(result.data);
|
|
this.stats.completed++;
|
|
this.updateRoutingTable(agent, true, result.latency);
|
|
} else {
|
|
agent.fail(result.error);
|
|
this.stats.failed++;
|
|
this.updateRoutingTable(agent, false, result.latency);
|
|
}
|
|
|
|
agent.ruvSpent = result.ruvSpent || agent.config.baseRuv;
|
|
this.stats.totalRuvSpent += agent.ruvSpent;
|
|
}
|
|
|
|
/**
|
|
* Update routing table with outcome
|
|
*/
|
|
updateRoutingTable(agent, success, latency) {
|
|
const key = `${agent.assignedNode}-${agent.type}`;
|
|
const existing = this.routingTable.get(key) || {
|
|
attempts: 0,
|
|
successes: 0,
|
|
totalLatency: 0,
|
|
};
|
|
|
|
existing.attempts++;
|
|
if (success) existing.successes++;
|
|
existing.totalLatency += latency || 0;
|
|
existing.successRate = existing.successes / existing.attempts;
|
|
existing.avgLatency = existing.totalLatency / existing.attempts;
|
|
|
|
this.routingTable.set(key, existing);
|
|
}
|
|
|
|
/**
|
|
* Get agent by ID
|
|
*/
|
|
getAgent(agentId) {
|
|
return this.agents.get(agentId);
|
|
}
|
|
|
|
/**
|
|
* List all agents
|
|
*/
|
|
listAgents(filter = {}) {
|
|
let agents = Array.from(this.agents.values());
|
|
|
|
if (filter.status) {
|
|
agents = agents.filter(a => a.status === filter.status);
|
|
}
|
|
if (filter.type) {
|
|
agents = agents.filter(a => a.type === filter.type);
|
|
}
|
|
|
|
return agents.map(a => a.getInfo());
|
|
}
|
|
|
|
/**
|
|
* Get spawner stats
|
|
*/
|
|
getStats() {
|
|
return {
|
|
...this.stats,
|
|
activeAgents: Array.from(this.agents.values())
|
|
.filter(a => a.status === TaskStatus.RUNNING).length,
|
|
queuedAgents: this.pendingQueue.length,
|
|
successRate: this.stats.completed /
|
|
(this.stats.completed + this.stats.failed) || 0,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Worker Pool
|
|
*
|
|
* Manages a pool of distributed workers for parallel task execution.
|
|
*/
|
|
export class WorkerPool extends EventEmitter {
|
|
constructor(networkManager, options = {}) {
|
|
super();
|
|
this.id = `pool-${randomBytes(6).toString('hex')}`;
|
|
this.network = networkManager;
|
|
this.size = options.size || 5;
|
|
this.capabilities = options.capabilities || ['compute', 'embed'];
|
|
this.maxTasksPerWorker = options.maxTasksPerWorker || 10;
|
|
|
|
this.workers = new Map();
|
|
this.taskQueue = [];
|
|
this.activeTasks = new Map();
|
|
this.results = new Map();
|
|
|
|
this.status = 'initializing';
|
|
this.stats = {
|
|
tasksCompleted: 0,
|
|
tasksFailed: 0,
|
|
totalProcessingTime: 0,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Initialize the worker pool
|
|
*/
|
|
async initialize() {
|
|
this.status = 'recruiting';
|
|
this.emit('status', 'Recruiting workers...');
|
|
|
|
// Find available workers from network
|
|
const peers = this.network?.getPeerList?.() ||
|
|
Array.from(this.network?.peers?.values() || []);
|
|
|
|
// Filter peers by capabilities
|
|
const eligiblePeers = peers.filter(peer => {
|
|
const peerCaps = peer.capabilities || [];
|
|
return this.capabilities.some(c => peerCaps.includes(c));
|
|
});
|
|
|
|
// Recruit up to pool size
|
|
const recruited = eligiblePeers.slice(0, this.size);
|
|
|
|
for (const peer of recruited) {
|
|
this.workers.set(peer.piKey || peer.id, {
|
|
id: peer.piKey || peer.id,
|
|
peer,
|
|
status: 'idle',
|
|
currentTasks: 0,
|
|
completedTasks: 0,
|
|
lastSeen: Date.now(),
|
|
});
|
|
}
|
|
|
|
// If not enough real workers, create virtual workers for local execution
|
|
while (this.workers.size < this.size) {
|
|
const virtualId = `virtual-${randomBytes(4).toString('hex')}`;
|
|
this.workers.set(virtualId, {
|
|
id: virtualId,
|
|
peer: null,
|
|
status: 'idle',
|
|
currentTasks: 0,
|
|
completedTasks: 0,
|
|
isVirtual: true,
|
|
});
|
|
}
|
|
|
|
this.status = 'ready';
|
|
this.emit('ready', {
|
|
poolId: this.id,
|
|
workers: this.workers.size,
|
|
realWorkers: Array.from(this.workers.values())
|
|
.filter(w => !w.isVirtual).length,
|
|
});
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Execute tasks in parallel across workers
|
|
*/
|
|
async execute(options) {
|
|
const {
|
|
task,
|
|
data,
|
|
strategy = 'parallel',
|
|
chunkSize = null,
|
|
} = options;
|
|
|
|
const batchId = `batch-${randomBytes(6).toString('hex')}`;
|
|
const startTime = Date.now();
|
|
|
|
// Split data into chunks for workers
|
|
let chunks;
|
|
if (Array.isArray(data)) {
|
|
const size = chunkSize || Math.ceil(data.length / this.workers.size);
|
|
chunks = [];
|
|
for (let i = 0; i < data.length; i += size) {
|
|
chunks.push(data.slice(i, i + size));
|
|
}
|
|
} else {
|
|
chunks = [data];
|
|
}
|
|
|
|
this.emit('batch-start', { batchId, chunks: chunks.length });
|
|
|
|
// Assign chunks to workers
|
|
const promises = chunks.map((chunk, index) =>
|
|
this.assignTask({
|
|
batchId,
|
|
index,
|
|
task,
|
|
data: chunk,
|
|
})
|
|
);
|
|
|
|
// Wait for all or handle based on strategy
|
|
let results;
|
|
if (strategy === 'parallel') {
|
|
results = await Promise.all(promises);
|
|
} else if (strategy === 'race') {
|
|
results = [await Promise.race(promises)];
|
|
} else {
|
|
// Sequential
|
|
results = [];
|
|
for (const promise of promises) {
|
|
results.push(await promise);
|
|
}
|
|
}
|
|
|
|
const endTime = Date.now();
|
|
this.stats.totalProcessingTime += endTime - startTime;
|
|
|
|
this.emit('batch-complete', {
|
|
batchId,
|
|
duration: endTime - startTime,
|
|
results: results.length,
|
|
});
|
|
|
|
// Flatten results if array
|
|
return Array.isArray(data) ? results.flat() : results[0];
|
|
}
|
|
|
|
/**
|
|
* Assign a single task to an available worker
|
|
*/
|
|
async assignTask(taskInfo) {
|
|
const taskId = `task-${randomBytes(6).toString('hex')}`;
|
|
|
|
// Find idle worker
|
|
const worker = this.findIdleWorker();
|
|
if (!worker) {
|
|
// Queue task
|
|
return new Promise((resolve, reject) => {
|
|
this.taskQueue.push({ taskInfo, resolve, reject });
|
|
});
|
|
}
|
|
|
|
worker.status = 'busy';
|
|
worker.currentTasks++;
|
|
|
|
this.activeTasks.set(taskId, {
|
|
...taskInfo,
|
|
workerId: worker.id,
|
|
startTime: Date.now(),
|
|
});
|
|
|
|
try {
|
|
// Execute on worker
|
|
const result = await this.executeOnWorker(worker, taskInfo);
|
|
|
|
worker.completedTasks++;
|
|
this.stats.tasksCompleted++;
|
|
this.results.set(taskId, result);
|
|
|
|
return result;
|
|
} catch (error) {
|
|
this.stats.tasksFailed++;
|
|
throw error;
|
|
} finally {
|
|
worker.currentTasks--;
|
|
if (worker.currentTasks === 0) {
|
|
worker.status = 'idle';
|
|
}
|
|
this.activeTasks.delete(taskId);
|
|
|
|
// Process queued task if any
|
|
if (this.taskQueue.length > 0) {
|
|
const { taskInfo, resolve, reject } = this.taskQueue.shift();
|
|
this.assignTask(taskInfo).then(resolve).catch(reject);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find an idle worker
|
|
*/
|
|
findIdleWorker() {
|
|
for (const worker of this.workers.values()) {
|
|
if (worker.status === 'idle' ||
|
|
worker.currentTasks < this.maxTasksPerWorker) {
|
|
return worker;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Execute task on a specific worker
|
|
*/
|
|
async executeOnWorker(worker, taskInfo) {
|
|
if (worker.isVirtual) {
|
|
// Local execution for virtual workers
|
|
return this.executeLocally(taskInfo);
|
|
}
|
|
|
|
// Send to remote worker via network
|
|
return new Promise((resolve, reject) => {
|
|
const timeout = setTimeout(() => {
|
|
reject(new Error('Worker timeout'));
|
|
}, 60000);
|
|
|
|
// Send task
|
|
if (this.network?.sendToPeer) {
|
|
this.network.sendToPeer(worker.id, {
|
|
type: 'worker_task',
|
|
poolId: this.id,
|
|
task: taskInfo.task,
|
|
data: taskInfo.data,
|
|
});
|
|
}
|
|
|
|
// Listen for result
|
|
const handler = (msg) => {
|
|
if (msg.poolId === this.id && msg.batchId === taskInfo.batchId) {
|
|
clearTimeout(timeout);
|
|
this.network?.off?.('worker_result', handler);
|
|
resolve(msg.result);
|
|
}
|
|
};
|
|
|
|
this.network?.on?.('worker_result', handler);
|
|
|
|
// Fallback to local if no response
|
|
setTimeout(() => {
|
|
clearTimeout(timeout);
|
|
this.executeLocally(taskInfo).then(resolve).catch(reject);
|
|
}, 5000);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Execute task locally (for virtual workers or fallback)
|
|
*/
|
|
async executeLocally(taskInfo) {
|
|
const { task, data } = taskInfo;
|
|
|
|
// Simple local execution based on task type
|
|
switch (task) {
|
|
case 'embed':
|
|
// Simulate embedding
|
|
return Array.isArray(data)
|
|
? data.map(() => new Array(384).fill(0).map(() => Math.random()))
|
|
: new Array(384).fill(0).map(() => Math.random());
|
|
|
|
case 'process':
|
|
return Array.isArray(data)
|
|
? data.map(item => ({ processed: true, item }))
|
|
: { processed: true, data };
|
|
|
|
case 'analyze':
|
|
return {
|
|
analyzed: true,
|
|
itemCount: Array.isArray(data) ? data.length : 1,
|
|
timestamp: Date.now(),
|
|
};
|
|
|
|
default:
|
|
return { task, data, executed: true };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get pool status
|
|
*/
|
|
getStatus() {
|
|
const workers = Array.from(this.workers.values());
|
|
return {
|
|
poolId: this.id,
|
|
status: this.status,
|
|
totalWorkers: workers.length,
|
|
idleWorkers: workers.filter(w => w.status === 'idle').length,
|
|
busyWorkers: workers.filter(w => w.status === 'busy').length,
|
|
virtualWorkers: workers.filter(w => w.isVirtual).length,
|
|
queuedTasks: this.taskQueue.length,
|
|
activeTasks: this.activeTasks.size,
|
|
stats: this.stats,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Shutdown the pool
|
|
*/
|
|
async shutdown() {
|
|
this.status = 'shutting_down';
|
|
|
|
// Wait for active tasks
|
|
while (this.activeTasks.size > 0) {
|
|
await new Promise(r => setTimeout(r, 100));
|
|
}
|
|
|
|
// Clear workers
|
|
this.workers.clear();
|
|
this.status = 'shutdown';
|
|
this.emit('shutdown');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Task Orchestrator
|
|
*
|
|
* Orchestrates multi-agent workflows and complex task pipelines.
|
|
*/
|
|
export class TaskOrchestrator extends EventEmitter {
|
|
constructor(agentSpawner, workerPool, options = {}) {
|
|
super();
|
|
this.spawner = agentSpawner;
|
|
this.pool = workerPool;
|
|
this.workflows = new Map();
|
|
this.maxConcurrentWorkflows = options.maxConcurrentWorkflows || 5;
|
|
}
|
|
|
|
/**
|
|
* Create a workflow
|
|
*/
|
|
createWorkflow(name, steps) {
|
|
const workflow = {
|
|
id: `wf-${randomBytes(6).toString('hex')}`,
|
|
name,
|
|
steps,
|
|
status: 'created',
|
|
currentStep: 0,
|
|
results: [],
|
|
startTime: null,
|
|
endTime: null,
|
|
};
|
|
|
|
this.workflows.set(workflow.id, workflow);
|
|
return workflow;
|
|
}
|
|
|
|
/**
|
|
* Execute a workflow
|
|
*/
|
|
async executeWorkflow(workflowId, input = {}) {
|
|
const workflow = this.workflows.get(workflowId);
|
|
if (!workflow) throw new Error('Workflow not found');
|
|
|
|
workflow.status = 'running';
|
|
workflow.startTime = Date.now();
|
|
workflow.input = input;
|
|
|
|
this.emit('workflow-start', { workflowId, name: workflow.name });
|
|
|
|
try {
|
|
let context = { ...input };
|
|
|
|
for (let i = 0; i < workflow.steps.length; i++) {
|
|
workflow.currentStep = i;
|
|
const step = workflow.steps[i];
|
|
|
|
this.emit('step-start', {
|
|
workflowId,
|
|
step: i,
|
|
type: step.type,
|
|
name: step.name,
|
|
});
|
|
|
|
const result = await this.executeStep(step, context);
|
|
workflow.results.push(result);
|
|
|
|
// Pass result to next step
|
|
context = { ...context, [step.name || `step${i}`]: result };
|
|
|
|
this.emit('step-complete', {
|
|
workflowId,
|
|
step: i,
|
|
result,
|
|
});
|
|
}
|
|
|
|
workflow.status = 'completed';
|
|
workflow.endTime = Date.now();
|
|
|
|
this.emit('workflow-complete', {
|
|
workflowId,
|
|
duration: workflow.endTime - workflow.startTime,
|
|
results: workflow.results,
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
results: workflow.results,
|
|
context,
|
|
};
|
|
|
|
} catch (error) {
|
|
workflow.status = 'failed';
|
|
workflow.endTime = Date.now();
|
|
workflow.error = error.message;
|
|
|
|
this.emit('workflow-failed', { workflowId, error: error.message });
|
|
|
|
return {
|
|
success: false,
|
|
error: error.message,
|
|
failedStep: workflow.currentStep,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute a single workflow step
|
|
*/
|
|
async executeStep(step, context) {
|
|
switch (step.type) {
|
|
case 'agent':
|
|
return this.executeAgentStep(step, context);
|
|
|
|
case 'parallel':
|
|
return this.executeParallelStep(step, context);
|
|
|
|
case 'pool':
|
|
return this.executePoolStep(step, context);
|
|
|
|
case 'condition':
|
|
return this.executeConditionStep(step, context);
|
|
|
|
case 'transform':
|
|
return this.executeTransformStep(step, context);
|
|
|
|
default:
|
|
throw new Error(`Unknown step type: ${step.type}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute an agent step
|
|
*/
|
|
async executeAgentStep(step, context) {
|
|
const task = typeof step.task === 'function'
|
|
? step.task(context)
|
|
: step.task;
|
|
|
|
const agent = await this.spawner.spawn({
|
|
type: step.agentType || 'researcher',
|
|
task,
|
|
maxRuv: step.maxRuv,
|
|
priority: step.priority,
|
|
});
|
|
|
|
return new Promise((resolve, reject) => {
|
|
agent.on('complete', resolve);
|
|
agent.on('error', reject);
|
|
|
|
// Simulate completion for now
|
|
setTimeout(() => {
|
|
agent.complete({
|
|
task,
|
|
result: `Completed: ${task}`,
|
|
context,
|
|
});
|
|
}, 1000);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Execute parallel agents
|
|
*/
|
|
async executeParallelStep(step, context) {
|
|
const promises = step.agents.map(agentConfig =>
|
|
this.executeAgentStep(agentConfig, context)
|
|
);
|
|
|
|
return Promise.all(promises);
|
|
}
|
|
|
|
/**
|
|
* Execute worker pool step
|
|
*/
|
|
async executePoolStep(step, context) {
|
|
const data = typeof step.data === 'function'
|
|
? step.data(context)
|
|
: step.data || context.data;
|
|
|
|
return this.pool.execute({
|
|
task: step.task,
|
|
data,
|
|
strategy: step.strategy || 'parallel',
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Execute conditional step
|
|
*/
|
|
async executeConditionStep(step, context) {
|
|
const condition = typeof step.condition === 'function'
|
|
? step.condition(context)
|
|
: step.condition;
|
|
|
|
if (condition) {
|
|
return this.executeStep(step.then, context);
|
|
} else if (step.else) {
|
|
return this.executeStep(step.else, context);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Execute transform step
|
|
*/
|
|
async executeTransformStep(step, context) {
|
|
return step.transform(context);
|
|
}
|
|
|
|
/**
|
|
* Get workflow status
|
|
*/
|
|
getWorkflowStatus(workflowId) {
|
|
const workflow = this.workflows.get(workflowId);
|
|
if (!workflow) return null;
|
|
|
|
return {
|
|
id: workflow.id,
|
|
name: workflow.name,
|
|
status: workflow.status,
|
|
currentStep: workflow.currentStep,
|
|
totalSteps: workflow.steps.length,
|
|
progress: (workflow.currentStep / workflow.steps.length) * 100,
|
|
startTime: workflow.startTime,
|
|
endTime: workflow.endTime,
|
|
duration: workflow.endTime && workflow.startTime
|
|
? workflow.endTime - workflow.startTime
|
|
: null,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* List all workflows
|
|
*/
|
|
listWorkflows() {
|
|
return Array.from(this.workflows.values()).map(w => ({
|
|
id: w.id,
|
|
name: w.name,
|
|
status: w.status,
|
|
steps: w.steps.length,
|
|
}));
|
|
}
|
|
}
|
|
|
|
// Export default instances
|
|
export default {
|
|
AGENT_TYPES,
|
|
TaskStatus,
|
|
DistributedAgent,
|
|
AgentSpawner,
|
|
WorkerPool,
|
|
TaskOrchestrator,
|
|
};
|