@notbnull/mcp-cursor-taskmaster
Version:
A project-scoped MCP server requiring explicit --project-dir configuration for task management, memory retrieval, and agentic workflows
415 lines • 16 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WorkflowEngine = void 0;
const promises_1 = require("timers/promises");
const approval_ui_js_1 = require("./approval-ui.js");
class WorkflowEngine {
taskStore;
memoryStore;
config;
constructor(taskStore, memoryStore, config = {}) {
this.taskStore = taskStore;
this.memoryStore = memoryStore;
this.config = {
pauseAfterEachTask: false,
pauseOnError: true,
maxRetries: 3,
checkpointFrequency: "error",
requireApproval: true,
...config,
};
}
/**
* Parse a user prompt and generate a hierarchical task structure
*/
async parsePromptToTasks(query, title, description) {
// Create the prompt record
const prompt = await this.taskStore.createPrompt({
title: title || "Generated from Query",
description: description ||
`Auto-generated prompt from: ${query.substring(0, 100)}...`,
originalQuery: query,
status: "draft",
taskIds: [],
});
// Store the original query in memory for future reference
await this.memoryStore.set(`prompt_${prompt.id}_query`, query, {
promptId: prompt.id,
type: "original_query",
});
// Log workflow action
await this.taskStore.logWorkflowAction("prompt_created", "success", undefined, { promptId: prompt.id, query: query.substring(0, 200) });
// For now, we'll create a basic task structure
// In a real implementation, you'd use an LLM to parse the query
const mainTask = await this.createTaskFromQuery(query, prompt.id);
// Update prompt with task ID
await this.taskStore.updatePrompt(prompt.id, {
taskIds: [mainTask.id],
});
return {
promptId: prompt.id,
taskIds: [mainTask.id],
};
}
/**
* Create a task from a query (simplified implementation)
*/
async createTaskFromQuery(query, promptId) {
// This is a simplified implementation. In practice, you'd use an LLM to break down the query
const task = await this.taskStore.createTask({
title: `Execute: ${query.substring(0, 50)}${query.length > 50 ? "..." : ""}`,
description: query,
status: "pending",
priority: "medium",
metadata: {
promptId,
type: "main_task",
needsUserApproval: true,
},
});
return task;
}
/**
* Request user approval for a set of tasks
*/
async requestUserApproval(taskIds) {
// Mark tasks as awaiting approval
for (const taskId of taskIds) {
await this.taskStore.updateTask(taskId, {
status: "awaiting_approval",
});
}
// Log workflow action
await this.taskStore.logWorkflowAction("user_approval_requested", "pending", undefined, { taskIds, timestamp: new Date().toISOString() });
// Store approval checkpoint
const state = await this.taskStore.getWorkflowState();
await this.taskStore.updateWorkflowState({
...state,
checkpointData: {
...state.checkpointData,
pendingApproval: {
taskIds,
requestedAt: new Date().toISOString(),
},
},
});
// If approval is not required, auto-approve
if (!this.config.requireApproval) {
return {
approved: true,
userFeedback: "Auto-approved (approval not required)",
};
}
// Get the approval UI server
const approvalUI = (0, approval_ui_js_1.getApprovalUIServer)();
// Request approval for each task
const approvalPromises = taskIds.map(async (taskId) => {
const task = await this.taskStore.getTask(taskId);
if (!task)
return null;
const prompt = task.metadata?.promptId
? await this.taskStore.getPrompt(task.metadata.promptId)
: null;
const request = {
taskId,
task,
promptTitle: prompt?.title,
promptDescription: prompt?.description,
};
return approvalUI.requestApproval(request);
});
// Wait for all approvals
const responses = await Promise.all(approvalPromises);
const validResponses = responses.filter((r) => r !== null);
// Process responses
const allApproved = validResponses.every((r) => r.approved);
const modifiedTasks = [];
const feedbacks = [];
for (const response of validResponses) {
if (response.modifications) {
await this.taskStore.updateTask(response.taskId, response.modifications);
modifiedTasks.push(response.taskId);
}
if (response.userFeedback) {
feedbacks.push(response.userFeedback);
}
}
return {
approved: allApproved,
modifiedTasks: modifiedTasks.length > 0 ? modifiedTasks : undefined,
userFeedback: feedbacks.length > 0 ? feedbacks.join("; ") : undefined,
};
}
/**
* Execute a workflow from a prompt
*/
async executeWorkflow(promptId) {
const prompt = await this.taskStore.getPrompt(promptId);
if (!prompt) {
throw new Error(`Prompt ${promptId} not found`);
}
// Update workflow state
await this.taskStore.updateWorkflowState({
currentPromptId: promptId,
});
// Update prompt status
await this.taskStore.updatePrompt(promptId, { status: "executing" });
const completedTasks = [];
const failedTasks = [];
try {
// Execute tasks in the prompt
for (const taskId of prompt.taskIds) {
const result = await this.executeTaskHierarchy(taskId);
if (result.success) {
completedTasks.push(...result.completedTasks);
}
else {
failedTasks.push(...result.failedTasks);
if (this.config.pauseOnError) {
await this.taskStore.updateWorkflowState({
currentTaskId: taskId,
checkpointData: {
error: result.error,
failedTasks: result.failedTasks,
},
});
return {
success: false,
completedTasks,
failedTasks,
pausedAt: taskId,
needsUserInput: true,
};
}
}
}
// Mark prompt as completed
await this.taskStore.updatePrompt(promptId, { status: "completed" });
return {
success: true,
completedTasks,
failedTasks,
};
}
catch (error) {
await this.taskStore.updatePrompt(promptId, { status: "failed" });
await this.taskStore.logWorkflowAction("workflow_error", "failed", undefined, { error: error instanceof Error ? error.message : "Unknown error" });
throw error;
}
}
/**
* Execute a task and its children hierarchically
*/
async executeTaskHierarchy(taskId) {
const task = await this.taskStore.getTask(taskId);
if (!task) {
return {
success: false,
completedTasks: [],
failedTasks: [taskId],
error: `Task ${taskId} not found`,
};
}
let completedTasks = [];
let failedTasks = [];
try {
// Check if task needs user approval
if (task.metadata?.needsUserApproval &&
task.status === "awaiting_approval") {
return {
success: false,
completedTasks: [],
failedTasks: [],
error: "Task awaiting user approval",
};
}
// Execute the task itself
await this.taskStore.updateTask(taskId, { status: "in_progress" });
await this.taskStore.logWorkflowAction("task_started", "in_progress", taskId);
// Simulate task execution (in practice, this would invoke actual tools/actions)
const executionResult = await this.executeTask(task);
if (executionResult.success) {
await this.taskStore.updateTask(taskId, {
status: "completed",
actualTime: executionResult.executionTime,
});
completedTasks.push(taskId);
await this.taskStore.logWorkflowAction("task_completed", "success", taskId, {
executionTime: executionResult.executionTime,
});
}
else {
await this.taskStore.updateTask(taskId, { status: "failed" });
failedTasks.push(taskId);
await this.taskStore.logWorkflowAction("task_failed", "failed", taskId, {
error: executionResult.error,
});
return {
success: false,
completedTasks,
failedTasks,
error: executionResult.error,
};
}
// Execute child tasks if any
if (task.children && task.children.length > 0) {
for (const childId of task.children) {
const childResult = await this.executeTaskHierarchy(childId);
completedTasks.push(...childResult.completedTasks);
failedTasks.push(...childResult.failedTasks);
if (!childResult.success && this.config.pauseOnError) {
return {
success: false,
completedTasks,
failedTasks,
error: childResult.error,
};
}
}
}
return {
success: failedTasks.length === 0,
completedTasks,
failedTasks,
};
}
catch (error) {
await this.taskStore.updateTask(taskId, { status: "failed" });
const errorMessage = error instanceof Error ? error.message : "Unknown error";
await this.taskStore.logWorkflowAction("task_error", "failed", taskId, {
error: errorMessage,
});
return {
success: false,
completedTasks,
failedTasks: [taskId],
error: errorMessage,
};
}
}
/**
* Execute a single task (placeholder implementation)
*/
async executeTask(task) {
const startTime = Date.now();
try {
// Store task execution context in memory
await this.memoryStore.set(`task_execution_${task.id}`, JSON.stringify({
task,
startTime: new Date().toISOString(),
status: "executing",
}), { taskId: task.id, type: "execution_context" });
// Simulate task execution
// In a real implementation, this would:
// 1. Parse the task description to determine what actions to take
// 2. Execute the appropriate tools/commands
// 3. Handle results and update task status
await (0, promises_1.setTimeout)(100); // Simulate work
const executionTime = Date.now() - startTime;
// Store execution result in memory
await this.memoryStore.set(`task_result_${task.id}`, JSON.stringify({
taskId: task.id,
success: true,
executionTime,
completedAt: new Date().toISOString(),
}), { taskId: task.id, type: "execution_result" });
return {
success: true,
executionTime,
};
}
catch (error) {
const executionTime = Date.now() - startTime;
const errorMessage = error instanceof Error ? error.message : "Unknown error";
// Store error in memory
await this.memoryStore.set(`task_error_${task.id}`, JSON.stringify({
taskId: task.id,
error: errorMessage,
executionTime,
failedAt: new Date().toISOString(),
}), { taskId: task.id, type: "execution_error" });
return {
success: false,
executionTime,
error: errorMessage,
};
}
}
/**
* Approve a task for execution
*/
async approveTask(taskId, userFeedback) {
const task = await this.taskStore.getTask(taskId);
if (!task)
return false;
await this.taskStore.updateTask(taskId, {
status: "pending",
metadata: {
...task.metadata,
userApproved: true,
userFeedback,
approvedAt: new Date().toISOString(),
},
});
await this.taskStore.logWorkflowAction("task_approved", "success", taskId, {
userFeedback,
});
return true;
}
/**
* Reject a task and optionally provide modifications
*/
async rejectTask(taskId, userFeedback, modifications) {
const task = await this.taskStore.getTask(taskId);
if (!task)
return false;
if (modifications) {
await this.taskStore.updateTask(taskId, {
...modifications,
status: "pending",
metadata: {
...task.metadata,
userRejected: true,
userFeedback,
rejectedAt: new Date().toISOString(),
modified: true,
},
});
}
else {
await this.taskStore.updateTask(taskId, {
status: "failed",
metadata: {
...task.metadata,
userRejected: true,
userFeedback,
rejectedAt: new Date().toISOString(),
},
});
}
await this.taskStore.logWorkflowAction("task_rejected", "failed", taskId, {
userFeedback,
hasModifications: !!modifications,
});
return true;
}
/**
* Resume workflow execution from a checkpoint
*/
async resumeWorkflow() {
const state = await this.taskStore.getWorkflowState();
if (!state.currentPromptId) {
throw new Error("No workflow to resume");
}
const prompt = await this.taskStore.getPrompt(state.currentPromptId);
if (!prompt) {
throw new Error("Current prompt not found");
}
// Resume from current task or start from beginning
const resumeFromTaskId = state.currentTaskId;
await this.taskStore.logWorkflowAction("workflow_resumed", "in_progress", resumeFromTaskId);
return await this.executeWorkflow(state.currentPromptId);
}
}
exports.WorkflowEngine = WorkflowEngine;
//# sourceMappingURL=workflow-engine.js.map