222 lines · 7.1 KB · TypeScript
RawDownload
/** * ErrorDetector service - Detects errors from parsed JSONL messages. * * This is the main orchestrator that coordinates between specialized modules: * - ToolSummaryFormatter: Formats tool information for display * - TriggerMatcher: Pattern matching utilities * - ToolResultExtractor: Extracts tool results from messages * - ErrorMessageBuilder: Builds error messages and DetectedError objects * - ErrorTriggerChecker: Checks different trigger types * - ErrorTriggerTester: Testing functionality for trigger preview * * Detection criteria: * - Uses configurable triggers from ConfigManager * - Supports tool_result triggers with requireError, toolName, and matchPattern * - Supports tool_use triggers for future expansion * - Supports token_threshold triggers for monitoring context usage */ import { type ParsedMessage } from '@main/types'; import {  buildToolResultMap,  buildToolUseMap,  type ToolResultInfo,  type ToolUseInfo,} from '../analysis/ToolResultExtractor';import { ConfigManager, type NotificationTrigger } from '../infrastructure/ConfigManager'; import { type DetectedError } from './ErrorMessageBuilder';import {  checkTokenThresholdTrigger,  checkToolResultTrigger,  checkToolUseTrigger,  matchesRepositoryScope,  preResolveRepositoryIds,} from './ErrorTriggerChecker';import { testTrigger as testTriggerImpl } from './ErrorTriggerTester'; // =============================================================================// Error Detector Class// ============================================================================= class ErrorDetector {  // ===========================================================================  // Main Detection Method  // ===========================================================================   /**   * Detects errors from an array of parsed messages using configurable triggers.   *   * @param messages - Array of ParsedMessage objects from a session   * @param sessionId - The session ID   * @param projectId - The project ID (encoded directory name)   * @param filePath - Path to the JSONL file   * @returns Array of DetectedError objects   */  async detectErrors(    messages: ParsedMessage[],    sessionId: string,    projectId: string,    filePath: string  ): Promise<DetectedError[]> {    const errors: DetectedError[] = [];     // Get enabled triggers from config    const configManager = ConfigManager.getInstance();    const triggers = configManager.getEnabledTriggers();     if (triggers.length === 0) {      return errors;    }     // Pre-resolve repository ID for this project to populate cache.    const cwdHint =      messages.find((message) => typeof message.cwd === 'string' && message.cwd.trim().length > 0)        ?.cwd ?? undefined;    await preResolveRepositoryIds([{ projectId, cwdHint }]);     // Build tool_use map for linking results to calls    const toolUseMap = buildToolUseMap(messages);    // Build tool_result map for estimating output tokens    const toolResultMap = buildToolResultMap(messages);     for (let i = 0; i < messages.length; i++) {      const message = messages[i];      const lineNumber = i + 1; // 1-based line numbers for JSONL       // Check each trigger against this message      for (const trigger of triggers) {        const triggerErrors = this.checkTrigger(          message,          trigger,          toolUseMap,          toolResultMap,          sessionId,          projectId,          filePath,          lineNumber        );         // Add all detected errors (can be multiple for token_threshold mode)        errors.push(...triggerErrors);      }    }     return errors;  }   // ===========================================================================  // Trigger Checking (Router)  // ===========================================================================   /**   * Checks if a message matches a specific trigger.   * Routes to the appropriate trigger checker based on trigger configuration.   *   * @param message - The parsed message to check   * @param trigger - The trigger configuration   * @param toolUseMap - Map of tool_use_id to tool_use content for linking results to calls   * @param toolResultMap - Map of tool_use_id to tool_result content for token estimation   * @param sessionId - Session ID   * @param projectId - Project ID   * @param filePath - File path   * @param lineNumber - Line number in JSONL   * @returns Array of DetectedError (can be multiple for token_threshold mode)   */  private checkTrigger(    message: ParsedMessage,    trigger: NotificationTrigger,    toolUseMap: Map<string, ToolUseInfo>,    toolResultMap: Map<string, ToolResultInfo>,    sessionId: string,    projectId: string,    filePath: string,    lineNumber: number  ): DetectedError[] {    // Check repository scope first - if repositoryIds is set, only trigger for matching repositories    if (!matchesRepositoryScope(projectId, trigger.repositoryIds)) {      return [];    }     // Use the mode directly (mode is now required in NotificationTrigger)    const effectiveMode = trigger.mode;     // Handle token_threshold mode - check each tool_use individually    if (effectiveMode === 'token_threshold') {      return checkTokenThresholdTrigger(        message,        trigger,        toolResultMap,        sessionId,        projectId,        filePath,        lineNumber      );    }     // Handle tool_result triggers    if (trigger.contentType === 'tool_result') {      const error = checkToolResultTrigger(        message,        trigger,        toolUseMap,        sessionId,        projectId,        filePath,        lineNumber      );      return error ? [error] : [];    }     // Handle tool_use triggers (for future expansion)    if (trigger.contentType === 'tool_use') {      const error = checkToolUseTrigger(        message,        trigger,        sessionId,        projectId,        filePath,        lineNumber      );      return error ? [error] : [];    }     return [];  }   // ===========================================================================  // Trigger Testing (Preview Feature)  // ===========================================================================   /**   * Tests a trigger configuration against historical session data.   * Returns a list of errors that would have been detected.   *   * Safety features (handled by ErrorTriggerTester):   * - Limits returned errors to 50   * - Caps totalCount at 10,000 to prevent indefinite counting   * - Stops scanning after 100 sessions   * - Aborts after 30 seconds   *   * @param trigger - The trigger configuration to test   * @param limit - Maximum number of results to return (default 50)   */  public async testTrigger(    trigger: NotificationTrigger,    limit: number = 50  ): Promise<{    totalCount: number;    errors: DetectedError[];    /** True if results were truncated due to safety limits */    truncated?: boolean;  }> {    return testTriggerImpl(trigger, limit);  }} // =============================================================================// Singleton Export// ============================================================================= export const errorDetector = new ErrorDetector();