@restnfeel/agentc-starter-kit
Version:
한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템
564 lines (489 loc) • 13.8 kB
text/typescript
/**
* Automation Pipeline Manager
* Handles task execution, scheduling, and monitoring for CMS automation
*/
import {
AutomationTask,
AutomationPipeline,
AutomationEvent,
ExecutionResult,
ScheduleConfig,
} from "./types.js";
export class AutomationPipelineManager {
private pipelines: Map<string, AutomationPipeline> = new Map();
private runningTasks: Map<string, Promise<ExecutionResult>> = new Map();
private eventHandlers: Map<string, ((event: AutomationEvent) => void)[]> =
new Map();
private nextTaskId = 1;
private nextPipelineId = 1;
/**
* Create a new automation task
*/
createTask(
taskData: Omit<AutomationTask, "id" | "createdAt" | "status">
): AutomationTask {
const task: AutomationTask = {
id: `task_${this.nextTaskId++}`,
createdAt: new Date(),
status: "pending",
...taskData,
};
this.emitEvent({
type: "task_created",
taskId: task.id,
timestamp: new Date(),
data: { task },
});
return task;
}
/**
* Create a new automation pipeline
*/
createPipeline(
pipelineData: Omit<AutomationPipeline, "id" | "createdAt" | "status">
): AutomationPipeline {
const pipeline: AutomationPipeline = {
id: `pipeline_${this.nextPipelineId++}`,
createdAt: new Date(),
status: "pending",
...pipelineData,
};
this.pipelines.set(pipeline.id, pipeline);
this.emitEvent({
type: "pipeline_created",
pipelineId: pipeline.id,
timestamp: new Date(),
data: { pipeline },
});
return pipeline;
}
/**
* Execute a single automation task
*/
async executeTask(task: AutomationTask): Promise<ExecutionResult> {
const startTime = Date.now();
this.emitEvent({
type: "task_started",
taskId: task.id,
timestamp: new Date(),
data: { task },
});
try {
// Simulate task execution based on type
const result = await this.performTaskExecution(task);
const executionTime = Date.now() - startTime;
const finalResult: ExecutionResult = {
...result,
taskId: task.id,
executionTime,
timestamp: new Date(),
};
this.emitEvent({
type: result.success ? "task_completed" : "task_failed",
taskId: task.id,
timestamp: new Date(),
data: { result: finalResult },
});
return finalResult;
} catch (error) {
const executionTime = Date.now() - startTime;
const errorResult: ExecutionResult = {
taskId: task.id,
success: false,
error: error instanceof Error ? error.message : "Unknown error",
executionTime,
timestamp: new Date(),
};
this.emitEvent({
type: "task_failed",
taskId: task.id,
timestamp: new Date(),
data: { error: errorResult.error },
});
return errorResult;
}
}
/**
* Execute an automation pipeline
*/
async executePipeline(pipelineId: string): Promise<{
success: boolean;
results: ExecutionResult[];
failedTasks: string[];
skippedTasks: string[];
}> {
const pipeline = this.pipelines.get(pipelineId);
if (!pipeline) {
throw new Error(`Pipeline not found: ${pipelineId}`);
}
this.emitEvent({
type: "pipeline_started",
pipelineId,
timestamp: new Date(),
data: { pipeline },
});
const results: ExecutionResult[] = [];
const failedTasks: string[] = [];
const skippedTasks: string[] = [];
try {
if (pipeline.executionMode === "parallel") {
// Execute all tasks in parallel
const taskPromises = pipeline.tasks.map((task) =>
this.executeTask(task)
);
const taskResults = await Promise.allSettled(taskPromises);
taskResults.forEach((result, index) => {
if (result.status === "fulfilled") {
results.push(result.value);
if (!result.value.success) {
failedTasks.push(pipeline.tasks[index].id);
}
} else {
const task = pipeline.tasks[index];
failedTasks.push(task.id);
results.push({
taskId: task.id,
success: false,
error: result.reason?.message || "Task execution failed",
executionTime: 0,
timestamp: new Date(),
});
}
});
} else {
// Execute tasks sequentially
for (const task of pipeline.tasks) {
// Check if we should skip this task due to previous failures
if (
pipeline.failureStrategy === "stop_on_failure" &&
failedTasks.length > 0
) {
skippedTasks.push(task.id);
continue;
}
const result = await this.executeTask(task);
results.push(result);
if (!result.success) {
failedTasks.push(task.id);
if (pipeline.failureStrategy === "stop_on_failure") {
// Skip remaining tasks
const remainingTasks = pipeline.tasks.slice(
pipeline.tasks.indexOf(task) + 1
);
skippedTasks.push(...remainingTasks.map((t) => t.id));
break;
}
}
}
}
const success = failedTasks.length === 0;
this.emitEvent({
type: success ? "pipeline_completed" : "pipeline_failed",
pipelineId,
timestamp: new Date(),
data: {
success,
totalTasks: pipeline.tasks.length,
failedTasks: failedTasks.length,
skippedTasks: skippedTasks.length,
},
});
return {
success,
results,
failedTasks,
skippedTasks,
};
} catch (error) {
this.emitEvent({
type: "pipeline_failed",
pipelineId,
timestamp: new Date(),
data: {
error: error instanceof Error ? error.message : "Unknown error",
},
});
throw error;
}
}
/**
* Retry a failed task with exponential backoff
*/
async retryTask(
task: AutomationTask,
maxRetries: number = 3,
backoffMs: number = 1000
): Promise<ExecutionResult> {
let lastError: string = "";
for (let attempt = 1; attempt <= maxRetries; attempt++) {
this.emitEvent({
type: "task_retry",
taskId: task.id,
timestamp: new Date(),
data: { attempt, maxRetries },
});
const result = await this.executeTask(task);
if (result.success) {
return result;
}
lastError = result.error || "Unknown error";
if (attempt < maxRetries) {
const delay = backoffMs * Math.pow(2, attempt - 1);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
return {
taskId: task.id,
success: false,
error: `Failed after ${maxRetries} attempts. Last error: ${lastError}`,
executionTime: 0,
timestamp: new Date(),
};
}
/**
* Schedule a pipeline for execution
*/
schedulePipeline(pipelineId: string, schedule: ScheduleConfig): void {
const pipeline = this.pipelines.get(pipelineId);
if (!pipeline) {
throw new Error(`Pipeline not found: ${pipelineId}`);
}
// In a real implementation, this would integrate with a job scheduler
// For now, we'll just emit an event
this.emitEvent({
type: "pipeline_scheduled",
pipelineId,
timestamp: new Date(),
data: { schedule },
});
}
/**
* Add event listener
*/
addEventListener(
eventType: string,
handler: (event: AutomationEvent) => void
): void {
if (!this.eventHandlers.has(eventType)) {
this.eventHandlers.set(eventType, []);
}
this.eventHandlers.get(eventType)!.push(handler);
}
/**
* Remove event listener
*/
removeEventListener(
eventType: string,
handler: (event: AutomationEvent) => void
): void {
const handlers = this.eventHandlers.get(eventType);
if (handlers) {
const index = handlers.indexOf(handler);
if (index > -1) {
handlers.splice(index, 1);
}
}
}
/**
* Get pipeline status
*/
getPipelineStatus(pipelineId: string): AutomationPipeline | undefined {
return this.pipelines.get(pipelineId);
}
/**
* Get all pipelines
*/
getAllPipelines(): AutomationPipeline[] {
return Array.from(this.pipelines.values());
}
/**
* Cancel a running pipeline
*/
async cancelPipeline(pipelineId: string): Promise<boolean> {
const pipeline = this.pipelines.get(pipelineId);
if (!pipeline) {
return false;
}
if (pipeline.status === "running") {
pipeline.status = "cancelled";
this.emitEvent({
type: "pipeline_cancelled",
pipelineId,
timestamp: new Date(),
data: { pipeline },
});
return true;
}
return false;
}
/**
* Rollback a pipeline execution
*/
async rollbackPipeline(pipelineId: string): Promise<{
success: boolean;
rollbackResults: ExecutionResult[];
}> {
const pipeline = this.pipelines.get(pipelineId);
if (!pipeline) {
throw new Error(`Pipeline not found: ${pipelineId}`);
}
this.emitEvent({
type: "pipeline_rollback_started",
pipelineId,
timestamp: new Date(),
data: { pipeline },
});
const rollbackResults: ExecutionResult[] = [];
// Execute rollback tasks in reverse order
for (const task of [...pipeline.tasks].reverse()) {
if (task.rollbackConfiguration) {
const rollbackTask: AutomationTask = {
...task,
id: `rollback_${task.id}`,
type: "rollback",
configuration: task.rollbackConfiguration,
};
const result = await this.executeTask(rollbackTask);
rollbackResults.push(result);
}
}
const success = rollbackResults.every((result) => result.success);
this.emitEvent({
type: success
? "pipeline_rollback_completed"
: "pipeline_rollback_failed",
pipelineId,
timestamp: new Date(),
data: { success, results: rollbackResults },
});
return {
success,
rollbackResults,
};
}
/**
* Get system health status
*/
getSystemHealth(): {
status: "healthy" | "warning" | "critical";
activePipelines: number;
runningTasks: number;
failedTasksLast24h: number;
systemLoad: number;
} {
const activePipelines = Array.from(this.pipelines.values()).filter(
(p) => p.status === "running"
).length;
const runningTasks = this.runningTasks.size;
// Simplified health calculation
let status: "healthy" | "warning" | "critical" = "healthy";
if (runningTasks > 10) {
status = "warning";
}
if (runningTasks > 20) {
status = "critical";
}
return {
status,
activePipelines,
runningTasks,
failedTasksLast24h: 0, // Would track this in a real implementation
systemLoad: Math.random() * 100, // Simulated system load
};
}
/**
* Perform the actual task execution based on task type
*/
private async performTaskExecution(
task: AutomationTask
): Promise<Omit<ExecutionResult, "taskId" | "executionTime" | "timestamp">> {
// Simulate execution time
const executionTime = Math.random() * 2000 + 500;
await new Promise((resolve) => setTimeout(resolve, executionTime));
// Simulate success/failure based on task type
const successRate = this.getTaskSuccessRate(task.type);
const success = Math.random() < successRate;
if (success) {
return {
success: true,
message: `${task.type} task completed successfully`,
output: this.generateTaskOutput(task),
};
} else {
return {
success: false,
error: `${task.type} task failed: Simulated failure`,
};
}
}
/**
* Get success rate for different task types
*/
private getTaskSuccessRate(taskType: string): number {
const rates: Record<string, number> = {
validation: 0.95,
deployment: 0.85,
backup: 0.98,
migration: 0.8,
test: 0.9,
cleanup: 0.95,
monitoring: 0.99,
rollback: 0.85,
};
return rates[taskType] || 0.85;
}
/**
* Generate sample output for tasks
*/
private generateTaskOutput(task: AutomationTask): Record<string, unknown> {
const baseOutput = {
taskId: task.id,
taskType: task.type,
environment: task.environment,
completedAt: new Date().toISOString(),
};
switch (task.type) {
case "deployment":
return {
...baseOutput,
deployedVersion: "1.2.3",
deploymentUrl: "https://example.com",
healthChecksPassed: true,
};
case "backup":
return {
...baseOutput,
backupLocation: "/backups/cms_backup_" + Date.now(),
backupSize: "2.3GB",
compressionRatio: 0.65,
};
case "test":
return {
...baseOutput,
testsRun: 142,
testsPassed: 140,
testsFailed: 2,
coverage: 0.94,
};
default:
return baseOutput;
}
}
/**
* Emit an automation event
*/
private emitEvent(event: AutomationEvent): void {
const handlers = this.eventHandlers.get(event.type);
if (handlers) {
handlers.forEach((handler) => {
try {
handler(event);
} catch (error) {
console.error("Error in event handler:", error);
}
});
}
}
}
// Export a default instance
export const automationPipeline = new AutomationPipelineManager();