Skip to main content
Glama

π“‚€π“’π“‹Ήπ”Έβ„•π•Œπ”Ήπ•€π•Šπ“‹Ήπ“’π“‚€ - Intelligent Guidance for

by Hive-Academy
step-progress-tracker.service.tsβ€’12.4 kB
import { Injectable, Inject } from '@nestjs/common'; import { IStepProgressRepository } from '../repositories/interfaces/step-progress.repository.interface'; import { isDefined, safeJsonCast, StepProgressError, } from '../utils/step-service-shared.utils'; import { getErrorMessage } from '../utils/type-safety.utils'; // =================================================================== // πŸ”₯ STEP PROGRESS TRACKER SERVICE - COMPLETE REVAMP FOR MCP-ONLY // =================================================================== // Purpose: Track MCP execution progress and provide progress analytics // Scope: MCP-focused progress tracking, step lifecycle management // ZERO Legacy Support: Complete removal of all non-MCP tracking logic // 🎯 STRICT TYPE DEFINITIONS - ZERO ANY USAGE - SCHEMA ALIGNED export interface StepProgressRecord { id: string; stepId: string; executionId: string; // πŸ”§ FIXED: Added required executionId taskId?: string; // πŸ”§ FIXED: Made optional (can be null for bootstrap) roleId: string; status: 'NOT_STARTED' | 'IN_PROGRESS' | 'COMPLETED' | 'SKIPPED' | 'FAILED'; startedAt?: Date; completedAt?: Date; failedAt?: Date; duration?: number; executionData?: McpExecutionData; validationResults?: unknown; errorDetails?: unknown; result?: 'SUCCESS' | 'FAILURE'; } export interface McpExecutionData { executionType: 'MCP_ONLY'; phase: 'GUIDANCE_PREPARED' | 'EXECUTING' | 'COMPLETED' | 'FAILED'; lastMcpResult?: McpExecutionResult; mcpResults?: McpExecutionResult[]; totalDuration?: number; errors?: string[]; } export interface McpProgressUpdate { completedActions: number; totalActions: number; lastResult: McpExecutionResult; } export interface StepCompletionData { result: 'SUCCESS' | 'FAILURE'; mcpResults: McpExecutionResult[]; duration: number; } export interface StepFailureData { errors: string[]; mcpResults?: McpExecutionResult[]; } export interface McpExecutionResult { actionId: string; actionName: string; success: boolean; output?: string; error?: string; executionTime?: number; } export interface RoleProgressSummary { roleId: string; totalSteps: number; completedSteps: number; failedSteps: number; inProgressSteps: number; averageExecutionTime: number; successRate: number; } // Custom Error Classes - Using shared utilities /** * πŸš€ REVAMPED: StepProgressTrackerService * * COMPLETE OVERHAUL FOR MCP-ONLY EXECUTION: * - Schema-aligned field names (executionData, not progressData) * - Correct enum values (IN_PROGRESS, SUCCESS/FAILURE) * - Correct table name (workflowStepProgress) * - Enhanced failure tracking with failedAt and result fields * - Zero legacy code - MCP-only focus * - Reduced dependencies: Only PrismaService */ @Injectable() export class StepProgressTrackerService { constructor( @Inject('IStepProgressRepository') private readonly stepProgressRepository: IStepProgressRepository, ) {} /** * Start step execution with MCP-focused tracking */ async startStep( stepId: string, executionId: string, taskId?: string, roleId?: string, ): Promise<StepProgressRecord> { try { // Get roleId from execution if not provided const actualRoleId = roleId; if (!actualRoleId) { // For now, throw an error if roleId is not provided // This could be enhanced later by injecting WorkflowExecutionRepository throw new Error(`roleId is required for step progress tracking`); } const progressRecord = await this.stepProgressRepository.create({ stepId, executionId, taskId: taskId || null, roleId: actualRoleId, status: 'IN_PROGRESS', startedAt: new Date(), executionData: { executionType: 'MCP_ONLY', phase: 'GUIDANCE_PREPARED', }, }); return this.transformProgressRecord(progressRecord); } catch (error) { throw new StepProgressError( `Failed to start progress tracking: ${getErrorMessage(error)}`, 'StepProgressTrackerService', 'startStep', { stepId }, ); } } /** * Update progress during MCP execution */ async updateProgress( stepId: string, update: McpProgressUpdate, ): Promise<StepProgressRecord> { try { const existing = await this.stepProgressRepository.findLatestByStep(stepId); if (!existing) { throw new Error(`No progress record found for step: ${stepId}`); } const currentData = safeJsonCast<McpExecutionData>( existing.executionData, ) || { executionType: 'MCP_ONLY', phase: 'EXECUTING', }; const progressRecord = await this.stepProgressRepository.update( existing.id, { executionData: JSON.parse( JSON.stringify({ ...currentData, phase: 'EXECUTING', lastMcpResult: update.lastResult, }), ), }, ); return this.transformProgressRecord(progressRecord); } catch (error) { throw new StepProgressError( `Failed to update progress: ${getErrorMessage(error)}`, 'StepProgressTrackerService', 'updateProgress', { stepId }, ); } } /** * Complete step execution tracking */ async completeStep( stepId: string, completion: StepCompletionData, ): Promise<StepProgressRecord> { try { const existing = await this.stepProgressRepository.findLatestByStep(stepId); if (!existing) { throw new Error(`No progress record found for step: ${stepId}`); } const currentData = safeJsonCast<McpExecutionData>( existing.executionData, ) || { executionType: 'MCP_ONLY', phase: 'COMPLETED', }; const progressRecord = await this.stepProgressRepository.update( existing.id, { status: 'COMPLETED', completedAt: new Date(), result: completion.result, duration: completion.duration, executionData: JSON.parse( JSON.stringify({ ...currentData, phase: 'COMPLETED', mcpResults: completion.mcpResults, totalDuration: completion.duration, }), ), }, ); return this.transformProgressRecord(progressRecord); } catch (error) { throw new StepProgressError( `Failed to complete step: ${getErrorMessage(error)}`, 'StepProgressTrackerService', 'completeStep', { stepId }, ); } } /** * Mark step as failed */ async failStep( stepId: string, failure: StepFailureData, ): Promise<StepProgressRecord> { try { const existing = await this.stepProgressRepository.findLatestByStep(stepId); if (!existing) { throw new Error(`No progress record found for step: ${stepId}`); } const progressRecord = await this.stepProgressRepository.update( existing.id, { status: 'FAILED', failedAt: new Date(), result: 'FAILURE', errorDetails: JSON.parse( JSON.stringify({ errors: failure.errors, mcpResults: failure.mcpResults, timestamp: new Date().toISOString(), }), ), }, ); return this.transformProgressRecord(progressRecord); } catch (error) { throw new StepProgressError( `Failed to mark step as failed: ${getErrorMessage(error)}`, 'StepProgressTrackerService', 'failStep', { stepId }, ); } } /** * Get current step progress */ async getStepProgress(stepId: string): Promise<StepProgressRecord | null> { try { const progressRecord = await this.stepProgressRepository.findLatestByStep(stepId); if (!progressRecord) { return null; } return this.transformProgressRecord(progressRecord); } catch (_error) { return null; } } /** * Get role execution statistics */ async getRoleProgressSummary(roleId: string): Promise<RoleProgressSummary> { try { const progressRecords = await this.stepProgressRepository.findByRoleId(roleId); const totalSteps = progressRecords.length; const completedSteps = progressRecords.filter( (p) => p.status === 'COMPLETED', ).length; const failedSteps = progressRecords.filter( (p) => p.status === 'FAILED', ).length; const inProgressSteps = progressRecords.filter( (p) => p.status === 'IN_PROGRESS', ).length; const completedRecords = progressRecords.filter( (p) => p.status === 'COMPLETED' && isDefined(p.duration), ); const averageExecutionTime = completedRecords.length > 0 ? completedRecords.reduce((sum, p) => sum + (p.duration || 0), 0) / completedRecords.length : 0; const successRate = totalSteps > 0 ? (completedSteps / totalSteps) * 100 : 0; return { roleId, totalSteps, completedSteps, failedSteps, inProgressSteps, averageExecutionTime, successRate, }; } catch (error) { throw new StepProgressError( `Failed to get role progress summary: ${getErrorMessage(error)}`, 'StepProgressTrackerService', 'getRoleProgressSummary', { roleId }, ); } } /** * Transform Prisma record to our interface */ private transformProgressRecord(record: unknown): StepProgressRecord { const typedRecord = record as { id: string; stepId: string; executionId: string; // πŸ”§ FIXED: Added executionId taskId: string | null; // πŸ”§ FIXED: Can be null now roleId: string; status: string; startedAt: Date | null; completedAt: Date | null; failedAt: Date | null; duration: number | null; executionData: unknown; validationResults: unknown; errorDetails: unknown; result: string | null; }; return { id: typedRecord.id, stepId: typedRecord.stepId, executionId: typedRecord.executionId, // πŸ”§ FIXED: Include executionId taskId: typedRecord.taskId ? typedRecord.taskId : undefined, // πŸ”§ FIXED: Convert null to undefined roleId: typedRecord.roleId, status: typedRecord.status as StepProgressRecord['status'], startedAt: typedRecord.startedAt || undefined, completedAt: typedRecord.completedAt || undefined, failedAt: typedRecord.failedAt || undefined, duration: typedRecord.duration || undefined, executionData: safeJsonCast<McpExecutionData>( typedRecord.executionData, ) || { executionType: 'MCP_ONLY', phase: 'GUIDANCE_PREPARED', }, validationResults: typedRecord.validationResults, errorDetails: typedRecord.errorDetails, result: (typedRecord.result as 'SUCCESS' | 'FAILURE') || undefined, }; } /** * Complete step with comprehensive tracking */ private async completeStepWithTracking( stepId: string, completionData: StepCompletionData, ): Promise<void> { try { const existing = await this.stepProgressRepository.findLatestByStep(stepId); if (!existing) { throw new Error(`No progress record found for step: ${stepId}`); } const endTime = new Date(); const duration = endTime.getTime() - (existing.startedAt?.getTime() || 0); const executionData = { phase: 'completion', lastMcpResult: completionData.mcpResults?.[0] || null, executionType: 'MCP_ONLY' as const, mcpResults: completionData.mcpResults || [], totalDuration: completionData.duration, }; await this.stepProgressRepository.update(existing.id, { status: 'COMPLETED', completedAt: endTime, duration, executionData: JSON.parse(JSON.stringify(executionData)), }); } catch (error) { throw new StepProgressError( `Failed to complete step with tracking: ${getErrorMessage(error)}`, 'StepProgressTrackerService', 'completeStepWithTracking', { stepId }, ); } } }

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/Hive-Academy/Anubis-MCP'

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