@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
459 lines (382 loc) • 15.1 kB
text/typescript
import {
Activity,
ActivityConfig,
ProcessExecution,
ConditionalBranch
} from './types.js';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
// import { AgentOrchestrator } from '../agent-orchestration/orchestrator.js';
// import { Agent } from '../agent-orchestration/types.js';
export class ActivityExecutor {
private processEngine: any; // Avoid circular dependency typing
private server?: Server;
// private agentOrchestrator: AgentOrchestrator;
constructor(processEngine: any, agentOrchestrator?: any) {
this.processEngine = processEngine;
// this.agentOrchestrator = agentOrchestrator;
}
setServer(server: Server): void {
this.server = server;
}
async execute(
activity: Activity,
inputs: Record<string, any>,
execution: ProcessExecution
): Promise<Record<string, any>> {
switch (activity.type) {
case 'tool':
return this.executeToolActivity(activity, inputs);
case 'human':
return this.executeHumanActivity(activity, inputs, execution);
case 'agent':
return this.executeAgentActivity(activity, inputs);
case 'conditional':
return this.executeConditionalActivity(activity, inputs, execution);
case 'loop':
return this.executeLoopActivity(activity, inputs, execution);
case 'parallel':
return this.executeParallelActivity(activity, inputs, execution);
case 'external':
return this.executeExternalActivity(activity, inputs);
default:
throw new Error(`Unknown activity type: ${activity.type}`);
}
}
private async executeToolActivity(
activity: Activity,
inputs: Record<string, any>
): Promise<Record<string, any>> {
const config = activity.config as ActivityConfig;
if (!config.toolName) {
throw new Error('Tool activity missing toolName');
}
if (!this.server) {
throw new Error('Server not initialized for tool execution');
}
// In a real implementation, this would call the actual MCP tool
// For now, we'll simulate the response
console.log(`Executing tool: ${config.toolName} with args:`, config.toolArgs);
// Simulate tool execution
return {
success: true,
toolName: config.toolName,
executedAt: new Date().toISOString(),
// Tool-specific outputs would go here
};
}
private async executeHumanActivity(
activity: Activity,
inputs: Record<string, any>,
execution: ProcessExecution
): Promise<Record<string, any>> {
const config = activity.config as ActivityConfig;
// Change execution status to waiting
execution.status = 'waiting';
this.processEngine.emit('execution:waiting', { execution, activity });
// In a real implementation, this would:
// 1. Create a task/notification for the assigned users
// 2. Wait for user response
// 3. Validate the response
// 4. Return the user's input
console.log(`Waiting for human input: ${activity.name}`);
console.log(`Assigned to: ${config.assignTo?.join(', ')}`);
console.log(`Prompt: ${config.prompt}`);
// For now, simulate immediate response
await new Promise(resolve => setTimeout(resolve, 1000));
execution.status = 'running';
return {
approved: true,
approvedBy: config.assignTo?.[0] || 'system',
approvedAt: new Date().toISOString(),
comments: 'Simulated approval'
};
}
private async executeAgentActivity(
activity: Activity,
inputs: Record<string, any>
): Promise<Record<string, any>> {
const config = activity.config as ActivityConfig;
if (!config.agentType) {
throw new Error('Agent activity missing agentType in config');
}
if (!config.agentTask) {
throw new Error('Agent activity missing agentTask in config');
}
try {
// 1. Create an orchestration session for this activity
const sessionName = `Process Activity: ${activity.name || 'Agent Task'}`;
// const session = this.agentOrchestrator.createSession(sessionName, config.agentTask);
throw new Error('Agent activities are not currently supported');
/* Agent code is commented out until agent-orchestration module is available
// 2. Create agent with appropriate template
const agentRole = this.mapAgentTypeToRole(config.agentType);
const template = this.getAgentTemplate(agentRole);
const agent = this.agentOrchestrator.createAgent(template, `${config.agentType}-${Date.now()}`);
// 3. Create a task for the agent
const capabilities = config.agentCapabilities || this.getDefaultCapabilities(agentRole);
const task = this.agentOrchestrator.createTask(
config.agentTask,
capabilities,
[] // no dependencies for now
);
// 4. Execute the task with the agent
const taskResult = await this.agentOrchestrator.executeTask(task.id);
// 5. Complete the session
this.agentOrchestrator.completeSession();
// 6. Return real agent results
return {
agentType: config.agentType,
agentId: agent.id,
taskId: task.id,
taskCompleted: taskResult.success,
results: taskResult.output || {},
artifacts: taskResult.artifacts || [],
duration: taskResult.performance?.duration || 0,
executionSummary: `Agent ${agent.name} completed task: ${config.agentTask}`,
sessionId: session.id
};
*/
} catch (error) {
console.error(`Agent activity failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
// Fallback to simulation if orchestration fails
console.log(`Falling back to simulated execution for agent: ${config.agentType}`);
console.log(`Task: ${config.agentTask}`);
return {
agentType: config.agentType,
agentId: 'fallback-agent',
taskId: 'fallback-task',
taskCompleted: false,
error: error instanceof Error ? error.message : 'Unknown error',
results: {
analysis: 'Agent execution failed - simulated fallback',
recommendations: ['Check agent orchestration configuration']
},
artifacts: [],
duration: 0,
executionSummary: `Fallback execution for ${config.agentType}: ${config.agentTask}`
};
}
}
private mapAgentTypeToRole(agentType: string): any /*Agent['role']*/ {
const roleMap: Record<string, any /*Agent['role']*/> = {
'researcher': 'researcher',
'coder': 'coder',
'reviewer': 'reviewer',
'tester': 'tester',
'documenter': 'documenter',
'analyzer': 'researcher', // Analyzer maps to researcher role
'developer': 'coder',
'qa': 'tester',
'coordinator': 'primary'
};
return roleMap[agentType.toLowerCase()] || 'primary';
}
private getAgentTemplate(role: any /*Agent['role']*/) {
const templates = {
primary: {
name: 'Primary Coordinator',
role: 'primary' as const,
systemPrompt: 'You are the primary coordinator. Manage the overall task and delegate to specialists.',
capabilities: ['coordination', 'planning', 'delegation', 'summary'],
},
researcher: {
name: 'Research Specialist',
role: 'researcher' as const,
systemPrompt: 'You are a research specialist. Find information and analyze requirements.',
capabilities: ['research', 'analysis', 'documentation', 'search'],
},
coder: {
name: 'Code Developer',
role: 'coder' as const,
systemPrompt: 'You are a code developer. Write clean, efficient, and tested code.',
capabilities: ['coding', 'debugging', 'refactoring', 'optimization'],
},
reviewer: {
name: 'Code Reviewer',
role: 'reviewer' as const,
systemPrompt: 'You are a code reviewer. Examine code for quality, security, and best practices.',
capabilities: ['review', 'quality-assurance', 'security', 'best-practices'],
},
tester: {
name: 'Quality Tester',
role: 'tester' as const,
systemPrompt: 'You are a quality tester. Design and execute tests to ensure software quality.',
capabilities: ['testing', 'automation', 'quality-assurance', 'bug-detection'],
},
documenter: {
name: 'Documentation Specialist',
role: 'documenter' as const,
systemPrompt: 'You are a documentation specialist. Create clear, comprehensive documentation.',
capabilities: ['documentation', 'writing', 'knowledge-management', 'tutorials'],
}
};
return templates[role];
}
private getDefaultCapabilities(role: any /*Agent['role']*/): string[] {
const capabilityMap: Record<any /*Agent['role']*/, string[]> = {
primary: ['coordination', 'planning', 'delegation'],
researcher: ['research', 'analysis', 'documentation'],
coder: ['coding', 'debugging', 'refactoring'],
reviewer: ['review', 'quality-assurance', 'security'],
tester: ['testing', 'automation', 'quality-assurance'],
documenter: ['documentation', 'writing', 'knowledge-management'],
custom: ['general', 'flexible', 'adaptable']
};
return capabilityMap[role] || ['general'];
}
private async executeConditionalActivity(
activity: Activity,
inputs: Record<string, any>,
execution: ProcessExecution
): Promise<Record<string, any>> {
const config = activity.config as ActivityConfig;
if (!config.conditions) {
throw new Error('Conditional activity missing conditions');
}
// Evaluate each condition
for (const branch of config.conditions) {
if (await this.evaluateCondition(branch.condition, execution.variables)) {
// Execute activities in this branch
const results: Record<string, any> = {};
for (const branchActivity of branch.activities) {
const result = await this.execute(branchActivity, inputs, execution);
Object.assign(results, result);
}
return results;
}
}
// Execute default branch if no conditions matched
if (config.defaultBranch) {
const results: Record<string, any> = {};
for (const defaultActivity of config.defaultBranch) {
const result = await this.execute(defaultActivity, inputs, execution);
Object.assign(results, result);
}
return results;
}
return { conditionMatched: false };
}
private async executeLoopActivity(
activity: Activity,
inputs: Record<string, any>,
execution: ProcessExecution
): Promise<Record<string, any>> {
const config = activity.config as ActivityConfig;
if (!config.collection || !config.activities) {
throw new Error('Loop activity missing collection or activities');
}
// Get collection from variables
const collection = execution.variables[config.collection] || [];
const itemVariable = config.itemVariable || 'item';
const maxIterations = config.maxIterations || 1000;
const results: any[] = [];
let iteration = 0;
for (const item of collection) {
if (iteration >= maxIterations) {
console.warn(`Loop reached max iterations (${maxIterations})`);
break;
}
// Set loop variable
execution.variables[itemVariable] = item;
execution.variables[`${itemVariable}Index`] = iteration;
// Execute loop activities
const iterationResults: Record<string, any> = {};
for (const loopActivity of config.activities) {
const result = await this.execute(loopActivity, inputs, execution);
Object.assign(iterationResults, result);
}
results.push(iterationResults);
iteration++;
}
// Clean up loop variables
delete execution.variables[itemVariable];
delete execution.variables[`${itemVariable}Index`];
return {
loopCompleted: true,
iterations: iteration,
results
};
}
private async executeParallelActivity(
activity: Activity,
inputs: Record<string, any>,
execution: ProcessExecution
): Promise<Record<string, any>> {
const config = activity.config as ActivityConfig;
if (!config.branches) {
throw new Error('Parallel activity missing branches');
}
const waitForAll = config.waitForAll !== false; // Default true
// Execute all branches in parallel
const branchPromises = config.branches.map(async (branch, index) => {
const branchResults: Record<string, any> = {};
try {
for (const branchActivity of branch) {
const result = await this.execute(branchActivity, inputs, execution);
Object.assign(branchResults, result);
}
return { success: true, results: branchResults, branchIndex: index };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : String(error),
branchIndex: index
};
}
});
if (waitForAll) {
// Wait for all branches to complete
const branchResults = await Promise.all(branchPromises);
return {
parallelCompleted: true,
branches: branchResults
};
} else {
// Return first completed branch
const firstResult = await Promise.race(branchPromises);
return {
parallelCompleted: true,
firstCompleted: firstResult
};
}
}
private async executeExternalActivity(
activity: Activity,
inputs: Record<string, any>
): Promise<Record<string, any>> {
const config = activity.config as ActivityConfig;
if (!config.url) {
throw new Error('External activity missing URL');
}
// In a real implementation, this would make an HTTP request
console.log(`Making external request to: ${config.url}`);
console.log(`Method: ${config.method || 'GET'}`);
// Simulate external call
await new Promise(resolve => setTimeout(resolve, 1500));
return {
status: 200,
response: {
message: 'External call completed',
data: { example: 'response data' }
}
};
}
private async evaluateCondition(
condition: string,
variables: Record<string, any>
): Promise<boolean> {
// In a real implementation, use a proper expression evaluator
// For now, simple string matching
console.log(`Evaluating condition: ${condition}`);
// Handle simple equality checks
const match = condition.match(/(\w+)\s*===?\s*(.+)/);
if (match) {
const [, varName, value] = match;
const varValue = variables[varName];
const expectedValue = value.replace(/['"]/g, '').trim();
return String(varValue) === expectedValue;
}
// Default to true for demo
return true;
}
}