Skip to main content
Glama
systempromptio

SystemPrompt Coding Agent

Official
task.ts14.6 kB
/** * @fileoverview Task management utilities for orchestrator tools providing * high-level operations for creating, updating, and managing tasks * @module handlers/tools/orchestrator/utils/task */ import { v4 as uuidv4 } from "uuid"; import { TaskStore } from "../../../services/task-store.js"; import { logger } from "../../../utils/logger.js"; import type { Task, TaskStatus, AITool, createTaskId, UpdateTaskParams, TaskLogEntry, } from "../../../types/task.js"; import { TASK_STATUS } from "../../../constants/task-status.js"; /** * Parameters for creating a new task */ export interface TaskCreationParams { title?: string; description: string; tool: AITool; projectPath: string; } /** * Task report structure */ export interface TaskReport { task: Task; duration: string; logCount: number; summary: string; } /** * High-level utilities for task management */ export class TaskOperations { public readonly taskStore: TaskStore; constructor(taskStore?: TaskStore) { this.taskStore = taskStore || TaskStore.getInstance(); } /** * Creates a new task * * @param params - Task creation parameters * @param sessionId - Optional session ID * @returns Created task object */ async createTask(params: TaskCreationParams, sessionId?: string): Promise<Task> { const taskId = uuidv4(); const now = new Date().toISOString(); const task: Task = { id: taskId as ReturnType<typeof createTaskId>, description: params.description, tool: params.tool, status: TASK_STATUS.PENDING, created_at: now, updated_at: now, logs: [], }; await this.taskStore.createTask(task, sessionId); logger.info("Task created", { taskId, tool: params.tool }); const logEntry: TaskLogEntry = { timestamp: new Date().toISOString(), level: 'info', type: 'system', message: `Task created with ${params.tool} tool`, metadata: { tool: params.tool, taskId, } }; await this.taskStore.addLog(taskId, logEntry, sessionId); return task; } /** * Updates task status with validation * * @param taskId - Task ID to update * @param newStatus - New task status * @param sessionId - Optional session ID * @param metadata - Optional metadata including error, result, etc * @returns Updated task or null if not found */ async updateTaskStatus( taskId: string, newStatus: TaskStatus, sessionId?: string, metadata?: { error?: string; result?: unknown; completedAt?: string; }, ): Promise<Task | null> { const task = await this.taskStore.getTask(taskId); if (!task) { logger.warn("Task not found for status update", { taskId }); return null; } if (!this.isValidStatusTransition(task.status, newStatus)) { logger.warn("Invalid status transition", { taskId, from: task.status, to: newStatus, }); throw new Error(`Cannot transition from ${task.status} to ${newStatus}`); } const updates: UpdateTaskParams = { status: newStatus, }; switch (newStatus) { case TASK_STATUS.IN_PROGRESS: if (!task.started_at) { updates.started_at = new Date().toISOString(); } break; case TASK_STATUS.WAITING: // Store the result for waiting tasks if (metadata?.result !== undefined && metadata.result !== null) { // Ensure result has the correct structure const result = metadata.result as any; if (result && typeof result === 'object' && 'output' in result && 'success' in result) { updates.result = result; } } break; case TASK_STATUS.COMPLETED: case TASK_STATUS.FAILED: case TASK_STATUS.CANCELLED: if (!task.completed_at) { updates.completed_at = metadata?.completedAt || new Date().toISOString(); } if (metadata?.error) { updates.error = metadata.error; } if (metadata?.result !== undefined && metadata.result !== null) { // Ensure result has the correct structure const result = metadata.result as any; if (result && typeof result === 'object' && 'output' in result && 'success' in result) { updates.result = result; } } break; } await this.taskStore.updateTask(taskId, updates, sessionId); const logEntry: TaskLogEntry = { timestamp: new Date().toISOString(), level: 'info', type: 'system', message: `Status: ${newStatus}`, metadata: { previousStatus: task.status, newStatus, taskId, } }; await this.taskStore.addLog(taskId, logEntry, sessionId); return { ...task, ...updates }; } /** * Checks if a status transition is valid * * @param from - Current status * @param to - Target status * @returns True if transition is valid */ private isValidStatusTransition(from: TaskStatus, to: TaskStatus): boolean { const validTransitions: Record<TaskStatus, TaskStatus[]> = { [TASK_STATUS.PENDING]: [TASK_STATUS.IN_PROGRESS, TASK_STATUS.CANCELLED], [TASK_STATUS.IN_PROGRESS]: [TASK_STATUS.WAITING, TASK_STATUS.COMPLETED, TASK_STATUS.FAILED, TASK_STATUS.CANCELLED], [TASK_STATUS.WAITING]: [TASK_STATUS.COMPLETED], [TASK_STATUS.COMPLETED]: [], [TASK_STATUS.FAILED]: [], [TASK_STATUS.CANCELLED]: [], }; return validTransitions[from]?.includes(to) || false; } /** * Generates a task report * * @param taskId - Task ID to report on * @param format - Report format (markdown, json, or summary) * @returns Formatted report string */ async generateTaskReport( taskId: string, format: "markdown" | "json" | "summary" = "markdown", ): Promise<string> { const task = await this.taskStore.getTask(taskId); if (!task) { throw new Error(`Task not found: ${taskId}`); } const logs = await this.taskStore.getLogs(taskId); const duration = this.calculateDuration(task); switch (format) { case "json": return JSON.stringify( { task, logs, duration, metrics: this.calculateTaskMetrics(task, logs), }, null, 2, ); case "summary": return this.generateSummaryReport(task, logs, duration); case "markdown": default: return this.generateMarkdownReport(task, logs, duration); } } /** * Generates a markdown report * * @param task - Task object * @param logs - Task log entries * @param duration - Formatted duration string * @returns Markdown formatted report */ private generateMarkdownReport(task: Task, logs: TaskLogEntry[], duration: string): string { const status = task.status === TASK_STATUS.COMPLETED ? "✅" : task.status === TASK_STATUS.WAITING ? "⏳✅" : task.status === TASK_STATUS.FAILED ? "❌" : task.status === TASK_STATUS.CANCELLED ? "⏹️" : task.status === TASK_STATUS.IN_PROGRESS ? "🔄" : "⏳"; let report = `# Task Report\n\n`; report += `**ID:** ${task.id}\n`; report += `**Status:** ${status} ${task.status}\n`; report += `**Tool:** ${task.tool === "CLAUDECODE" ? "Claude Code" : "Gemini CLI"}\n`; report += `**Duration:** ${duration}\n`; report += `**Created:** ${new Date(task.created_at).toLocaleString()}\n`; if (task.started_at) { report += `**Started:** ${new Date(task.started_at).toLocaleString()}\n`; } if (task.completed_at) { report += `**Completed:** ${new Date(task.completed_at).toLocaleString()}\n`; } report += `\n## Description\n\n${task.description}\n`; if (task.error) { report += `\n## Error\n\n\`\`\`\n${task.error}\n\`\`\`\n`; } if (logs.length > 0) { report += `\n## Execution Logs (${logs.length} entries)\n\n`; report += "```\n"; report += logs.slice(-50).map(log => { const prefix = log.prefix ? `[${log.prefix}]` : ''; const level = log.level !== 'info' ? `[${log.level.toUpperCase()}]` : ''; return `[${log.timestamp}] ${level}${prefix} ${log.message}`; }).join("\n"); report += "\n```\n"; } return report; } /** * Generates a summary report * * @param task - Task object * @param logs - Task log entries * @param duration - Formatted duration string * @returns Summary text report */ private generateSummaryReport(task: Task, logs: TaskLogEntry[], duration: string): string { const errorCount = logs.filter((log) => log.level === 'error').length; const warningCount = logs.filter((log) => log.level === 'warn').length; return [ `Task ID: ${task.id}`, `Status: ${task.status}`, `Duration: ${duration}`, `Logs: ${logs.length} entries (${errorCount} errors, ${warningCount} warnings)`, task.error ? `Error: ${task.error.substring(0, 100)}...` : "", ] .filter(Boolean) .join("\n"); } /** * Calculates task duration * * @param task - Task object * @returns Human-readable duration string */ private calculateDuration(task: Task): string { if (!task.started_at) { return "Not started"; } const start = new Date(task.started_at).getTime(); const end = task.completed_at ? new Date(task.completed_at).getTime() : Date.now(); const seconds = Math.floor((end - start) / 1000); if (seconds < 60) { return `${seconds}s`; } const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; if (minutes < 60) { return `${minutes}m ${remainingSeconds}s`; } const hours = Math.floor(minutes / 60); const remainingMinutes = minutes % 60; return `${hours}h ${remainingMinutes}m ${remainingSeconds}s`; } /** * Calculates task metrics * * @param task - Task object * @param logs - Task log entries * @returns Task metrics object */ private calculateTaskMetrics(task: Task, logs: TaskLogEntry[]): Record<string, unknown> { return { totalLogs: logs.length, errorCount: logs.filter((log) => log.level === 'error').length, warningCount: logs.filter((log) => log.level === 'warn').length, toolCalls: logs.filter((log) => log.type === 'tool').length, agentMessages: logs.filter((log) => log.type === 'agent').length, systemEvents: logs.filter((log) => log.type === 'system').length, outputLines: logs.filter((log) => log.type === 'output').length, status: task.status, }; } /** * Updates a task * * @param taskId - Task ID to update * @param updates - Partial task updates * @param sessionId - Optional session ID * @returns Updated task or null */ async updateTask( taskId: string, updates: Partial<Task>, sessionId?: string, ): Promise<Task | null> { return this.taskStore.updateTask(taskId, updates, sessionId); } /** * Adds a log entry * * @param taskId - Task ID to add log to * @param log - Log message or entry object * @param sessionId - Optional session ID */ async addTaskLog(taskId: string, log: string | TaskLogEntry, sessionId?: string): Promise<void> { await this.taskStore.addLog(taskId, log, sessionId); } /** * Gets task statistics * * @returns Task statistics including counts by status and tool */ async getTaskStatistics(): Promise<{ total: number; byStatus: Record<TaskStatus, number>; byTool: Record<string, number>; averageDuration: number; successRate: number; }> { const tasks = await this.taskStore.getTasks(); const byStatus = tasks.reduce( (acc: Record<TaskStatus, number>, task: Task) => { acc[task.status] = (acc[task.status] || 0) + 1; return acc; }, {} as Record<TaskStatus, number>, ); const byTool = tasks.reduce( (acc: Record<string, number>, task: Task) => { acc[task.tool] = (acc[task.tool] || 0) + 1; return acc; }, {} as Record<string, number>, ); const completedTasks = tasks.filter((t: Task) => t.status === TASK_STATUS.COMPLETED); const totalDuration = completedTasks.reduce((sum: number, task: Task) => { if (task.started_at && task.completed_at) { const start = new Date(task.started_at).getTime(); const end = new Date(task.completed_at).getTime(); return sum + Math.floor((end - start) / 1000); } return sum; }, 0); const successRate = tasks.length > 0 ? (completedTasks.length / tasks.length) * 100 : 0; return { total: tasks.length, byStatus, byTool, averageDuration: completedTasks.length > 0 ? totalDuration / completedTasks.length : 0, successRate, }; } } /** * Singleton instance of task operations - initialized lazily to avoid circular dependencies */ let _taskOperations: TaskOperations | null = null; /** * Export singleton with lazy initialization */ export const taskOperations = { get instance(): TaskOperations { if (!_taskOperations) { _taskOperations = new TaskOperations(); } return _taskOperations; }, // Direct property access get taskStore() { return this.instance.taskStore; }, // Method delegations createTask: (...args: Parameters<TaskOperations['createTask']>) => taskOperations.instance.createTask(...args), updateTaskStatus: (...args: Parameters<TaskOperations['updateTaskStatus']>) => taskOperations.instance.updateTaskStatus(...args), updateTask: (...args: Parameters<TaskOperations['updateTask']>) => taskOperations.instance.updateTask(...args), addTaskLog: (...args: Parameters<TaskOperations['addTaskLog']>) => taskOperations.instance.addTaskLog(...args), generateTaskReport: (...args: Parameters<TaskOperations['generateTaskReport']>) => taskOperations.instance.generateTaskReport(...args), getTaskStatistics: (...args: Parameters<TaskOperations['getTaskStatistics']>) => taskOperations.instance.getTaskStatistics(...args), // Delegate to taskStore getTask: (taskId: string) => taskOperations.instance.taskStore.getTask(taskId), getAllTasks: () => taskOperations.instance.taskStore.getAllTasks(), deleteTask: (taskId: string) => taskOperations.instance.taskStore.deleteTask(taskId), };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systempromptio/systemprompt-code-orchestrator'

If you have feedback or need assistance with the MCP directory API, please join our Discord server