devin-workflow
Version:
~Devin AI workflow automation
264 lines (232 loc) ⢠10.1 kB
JavaScript
import {WorkflowParser} from './workflow-parser.js';
import {DevinClient} from './devin-client.js';
import {HandoffManager} from './handoff-manager.js';
import 'dotenv/config';
/**
* One-click workflow execution function
* Handles everything automatically with sensible defaults
*
* @param {string} workflow - Markdown workflow content
* @param {Object} options - Optional configuration
* @returns {Promise<Object>} Execution results with detailed metrics
*/
export async function startWorkflow(workflow, options = {}) {
const startTime = Date.now();
try {
// Extract options with defaults
const defaultOptions = {
apiKey: process.env.DEVIN_API_KEY,
useMockMode: !process.env.DEVIN_API_KEY || process.env.NODE_ENV === 'test',
pollingInterval: 10, // seconds
firstPollingInterval: 90, // seconds (90 seconds for first poll)
timeout: 300, // seconds (5 minutes)
verbose: true,
mockApiUrl: 'http://localhost:3001/v1',
...options,
};
const {
apiKey = process.env.DEVIN_API_KEY,
useMockMode = !process.env.DEVIN_API_KEY || process.env.NODE_ENV === 'test',
pollingInterval = 10, // seconds
firstPollingInterval = 90, // seconds
timeout = 300, // seconds (5 minutes)
verbose = true,
mockApiUrl = 'http://localhost:3001/v1',
} = defaultOptions;
if (verbose) {
console.log('š Starting One-Click Workflow Execution');
console.log('=========================================');
console.log(`š Configuration:`);
console.log(` ⢠Mock Mode: ${useMockMode ? 'Enabled' : 'Disabled'}`);
console.log(` ⢠First Polling Interval: ${firstPollingInterval}s`);
console.log(` ⢠Polling Interval: ${pollingInterval}s`);
console.log(` ⢠Timeout: ${timeout}s`);
console.log(` ⢠API Key: ${apiKey ? apiKey.substring(0, 10) + '...' : 'Not provided'}\n`);
}
// Initialize components
const parser = new WorkflowParser();
const client = new DevinClient();
const handoffManager = new HandoffManager(client);
// Configure client
client.setApiKey(apiKey);
if (useMockMode) {
client.setMockMode(true, mockApiUrl);
if (verbose) {
console.log('š§ Mock mode enabled - using simulated Devin API');
}
}
// Configure handoff manager
handoffManager.setPollingInterval(pollingInterval * 1000); // Convert to milliseconds
handoffManager.setFirstPollingInterval(firstPollingInterval * 1000); // Convert to milliseconds
handoffManager.setTimeout(timeout * 1000); // Convert to milliseconds
// Parse workflow
if (verbose) {
console.log('š Parsing workflow...');
}
const parsed = parser.parse(workflow);
const steps = parsed.steps;
const validation = parser.validateWorkflow(steps);
if (!validation.valid) {
throw new Error(`Workflow validation failed: ${validation.errors.join(', ')}`);
}
if (verbose) {
console.log(` ā
Parsed ${steps.length} steps successfully`);
if (validation.warnings.length > 0) {
console.log(` ā ļø Warnings: ${validation.warnings.join(', ')}`);
}
// Show workflow summary
const summary = parser.formatWorkflowSummary(steps);
console.log(` š Workflow Summary:`);
console.log(` ⢠Total steps: ${summary.total_steps}`);
console.log(` ⢠Steps with playbooks: ${summary.steps_with_playbooks}`);
console.log(` ⢠Steps with handoffs: ${summary.steps_with_handoffs}`);
console.log(` ⢠Steps with repos: ${summary.steps_with_repos}`);
console.log(` ⢠Steps relying on previous: ${summary.steps_relying_on_previous}\n`);
}
// Execute workflow
if (verbose) {
console.log('\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n');
// console.log('āļø Executing workflow...\n', steps);
}
const results = await handoffManager.executeWorkflow(steps);
const executionTime = Date.now() - startTime;
// Generate comprehensive results
const successfulSteps = results.filter(r => r.success);
const failedSteps = results.filter(r => !r.success);
console.log('āļø After executeWorkflow => results...\n', results);
const stepsWithHandoffs = successfulSteps.filter(r => r.handoff_result);
if (verbose) {
console.log('\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n');
console.log('āļø stepsWithHandoffs...\n', stepsWithHandoffs);
}
const executionSummary = {
success: failedSteps.length === 0,
total_steps: results.length,
successful_steps: successfulSteps.length,
failed_steps: failedSteps.length,
steps_with_handoffs: stepsWithHandoffs.length,
total_execution_time_ms: executionTime,
average_step_time_ms: successfulSteps.length > 0 ? successfulSteps.reduce((sum, r) => sum + r.execution_time_ms, 0) / successfulSteps.length : 0,
session_ids: successfulSteps.map(r => r.session_id),
completion_rate: results.length > 0 ? (successfulSteps.length / results.length) * 100 : 0,
workflow_config: {
mock_mode: useMockMode,
polling_interval: pollingInterval,
first_polling_interval: firstPollingInterval,
timeout: timeout,
},
step_results: results,
executed_at: new Date().toISOString(),
};
if (verbose) {
console.log('\nšÆ Execution Complete!');
console.log('=====================');
console.log(`ā
Status: ${executionSummary.success ? 'SUCCESS' : 'FAILED'}`);
console.log(`š Steps: ${executionSummary.successful_steps}/${executionSummary.total_steps} completed`);
console.log(`ā±ļø Total Time: ${formatDuration(executionTime)}`);
console.log(`š Completion Rate: ${executionSummary.completion_rate.toFixed(1)}%`);
console.log(`šÆ Average Step Time: ${formatDuration(executionSummary.average_step_time_ms)}`);
if (executionSummary.steps_with_handoffs > 0) {
console.log(`š Handoffs Completed: ${executionSummary.steps_with_handoffs}`);
}
if (failedSteps.length > 0) {
console.log(`\nā Failed Steps:`);
failedSteps.forEach(step => {
console.log(` ⢠Step ${step.step_number}: ${step.error}`);
});
}
console.log(`\nš Session IDs generated: ${executionSummary.session_ids.length}`);
executionSummary.session_ids.forEach((sessionId, index) => {
console.log(` ${index + 1}. ${sessionId}`);
});
}
return executionSummary;
} catch (error) {
const executionTime = Date.now() - startTime;
if (options.verbose !== false) {
console.error(`\nā Workflow execution failed after ${formatDuration(executionTime)}`);
console.error(`Error: ${error.message}`);
}
return {
success: false,
error: error.message,
total_execution_time_ms: executionTime,
executed_at: new Date().toISOString(),
workflow_config: {
mock_mode: options.useMockMode,
polling_interval: options.pollingInterval,
first_polling_interval: options.firstPollingInterval,
timeout: options.timeout,
},
};
}
}
/**
* Quick workflow execution with minimal output
* Perfect for programmatic use
*/
export async function startWorkflowQuiet(workflow, options = {}) {
return startWorkflow(workflow,
{
...options,
verbose: false,
});
}
/**
* Execute workflow with mock mode (for testing)
*/
export async function startWorkflowMock(workflow, options = {}) {
return startWorkflow(workflow, {
...options,
useMockMode: true,
apiKey: 'mock-test-key',
pollingInterval: 2, // Faster for testing
firstPollingInterval: 2, // Faster for testing
timeout: 60, // Shorter timeout for testing
});
}
/**
* Validate workflow without executing
*/
export function validateWorkflow(workflow) {
try {
const parser = new WorkflowParser();
const steps = parser.parse(workflow);
const validation = parser.validateWorkflow(steps);
const summary = parser.formatWorkflowSummary(steps);
return {
valid: validation.valid,
errors: validation.errors,
warnings: validation.warnings,
steps: steps,
summary: summary,
};
} catch (error) {
return {
valid: false,
errors: [error.message],
warnings: [],
steps: [],
summary: null,
};
}
}
// Helper function to format duration
function formatDuration(milliseconds) {
const totalSeconds = Math.floor(milliseconds / 1000);
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
if (minutes === 0) {
return `${seconds}s`;
} else if (seconds === 0) {
return `${minutes}m`;
} else {
return `${minutes}m ${seconds}s`;
}
}
// Export individual components for advanced users
export {
WorkflowParser,
DevinClient,
HandoffManager,
};