diff options
Diffstat (limited to 'js/scripting-lang/scripting-harness/core')
-rw-r--r-- | js/scripting-lang/scripting-harness/core/harness.js | 263 |
1 files changed, 232 insertions, 31 deletions
diff --git a/js/scripting-lang/scripting-harness/core/harness.js b/js/scripting-lang/scripting-harness/core/harness.js index 817f4fa..313618b 100644 --- a/js/scripting-lang/scripting-harness/core/harness.js +++ b/js/scripting-lang/scripting-harness/core/harness.js @@ -51,24 +51,29 @@ class FunctionalHarness { async initialize() { if (!this.interpreter) { // Try different possible paths for lang.js + // The harness is in scripting-harness/core/, so we need to go up to find lang.js const possiblePaths = [ - '../../lang.js', - '../../../lang.js', - './lang.js', - '../lang.js' + '../../lang.js', // From scripting-harness/core/ to root + '../../../lang.js', // Fallback + './lang.js', // Current directory + '../lang.js', // Parent directory + '../../../../lang.js' // Extra fallback ]; + let lastError = null; + for (const path of possiblePaths) { try { this.interpreter = await import(path); break; } catch (error) { + lastError = error; // Continue to next path } } if (!this.interpreter) { - throw new Error('Could not find lang.js interpreter'); + throw new Error(`Could not find lang.js interpreter. Tried paths: ${possiblePaths.join(', ')}. Last error: ${lastError?.message}`); } } return this.interpreter; @@ -160,15 +165,54 @@ class FunctionalHarness { } /** - * Classify error types for granular error handling + * Classify errors for recovery strategies * - * @param {Error} error - Error object - * @returns {string} Error type classification + * @param {Error} error - Error to classify + * @returns {string} Error classification */ classifyError(error) { - if (error.message.includes('syntax')) return 'script_syntax_error'; - if (error.message.includes('timeout')) return 'harness_timeout_error'; - if (error.message.includes('network')) return 'adapter_network_error'; + const message = error.message.toLowerCase(); + + // Script execution errors + if (message.includes('unexpected token') || + message.includes('syntax error') || + message.includes('parse') || + message.includes('lexer')) { + return 'script_error'; + } + + // Timeout errors + if (message.includes('timeout') || + message.includes('timed out')) { + return 'timeout_error'; + } + + // Network errors + if (message.includes('network') || + message.includes('fetch') || + message.includes('http') || + message.includes('connection') || + message.includes('econnrefused') || + message.includes('enotfound')) { + return 'network_error'; + } + + // File system errors + if (message.includes('file') || + message.includes('fs') || + message.includes('enoent') || + message.includes('eperm')) { + return 'filesystem_error'; + } + + // Memory errors + if (message.includes('memory') || + message.includes('heap') || + message.includes('out of memory')) { + return 'memory_error'; + } + + // Default to unknown error return 'unknown_error'; } @@ -238,24 +282,6 @@ class FunctionalHarness { } /** - * Replay from version - * - * @param {number} startVersion - Starting version - * @param {Object} newState - New state to merge - * @returns {Promise<Object>} Promise resolving to replay result - */ - async replayFromVersion(startVersion, newState) { - const historicalState = this.stateHistory.getVersion(startVersion); - if (!historicalState) { - throw new Error(`Version ${startVersion} not found`); - } - - // Merge historical state with new state - const mergedState = { ...historicalState, ...newState }; - return this.update(mergedState); - } - - /** * Create branch from specific version * * @param {number} fromVersion - Base version @@ -268,11 +294,185 @@ class FunctionalHarness { throw new Error(`Version ${fromVersion} not found`); } - return new FunctionalHarness(this.scriptPath, { + // Create new harness with branch configuration + const branchHarness = new FunctionalHarness(this.scriptPath || this.scriptContent, { ...this.config, branchName, - baseVersion: fromVersion + baseVersion: fromVersion, + parentHarness: this }); + + // Initialize the branch harness + await branchHarness.initialize(); + + // Set the initial state to the base version + branchHarness.currentVersion = fromVersion; + branchHarness.stateHistory = this.stateHistory; // Share history + + console.log(`[Harness] Created branch '${branchName}' from version ${fromVersion}`); + + return branchHarness; + } + + /** + * Get branch information + * + * @returns {Object} Branch metadata + */ + getBranchInfo() { + return { + branchName: this.config.branchName || 'main', + baseVersion: this.config.baseVersion || 0, + currentVersion: this.currentVersion, + parentHarness: this.config.parentHarness ? true : false + }; + } + + /** + * Get state diff between versions + * + * @param {number} fromVersion - Starting version + * @param {number} toVersion - Ending version (defaults to current) + * @returns {Object|null} Diff object or null if versions not found + */ + getStateDiff(fromVersion, toVersion = this.currentVersion) { + const diff = this.stateHistory.getDiff(fromVersion, toVersion); + + if (diff) { + console.log(`[Harness] State diff from v${fromVersion} to v${toVersion}:`); + console.log(` Added: ${Object.keys(diff.added).length} properties`); + console.log(` Removed: ${Object.keys(diff.removed).length} properties`); + console.log(` Changed: ${Object.keys(diff.changed).length} properties`); + } + + return diff; + } + + /** + * Enhanced error recovery with retry mechanism + * + * @param {Function} operation - Operation to retry + * @param {Object} options - Retry options + * @returns {Promise<Object>} Operation result + */ + async retryOperation(operation, options = {}) { + const { + maxRetries = 3, + backoff = 2, + onRetry = null + } = options; + + let delay = options.delay || 1000; + let lastError; + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + return await operation(); + } catch (error) { + lastError = error; + + if (attempt === maxRetries) { + console.error(`[Harness] Operation failed after ${maxRetries} attempts:`, error.message); + throw error; + } + + console.log(`[Harness] Attempt ${attempt} failed, retrying in ${delay}ms...`); + + if (onRetry) { + onRetry(attempt, error); + } + + await new Promise(resolve => setTimeout(resolve, delay)); + delay *= backoff; + } + } + } + + /** + * Recover from error state + * + * @param {Error} error - The error that occurred + * @param {Object} context - Error context + * @returns {Promise<Object>} Recovery result + */ + async recoverFromError(error, context = {}) { + const errorType = this.classifyError(error); + + console.log(`[Harness] Attempting to recover from ${errorType} error:`, error.message); + + switch (errorType) { + case 'script_error': + // For script errors, try to rollback to last known good state + if (this.currentVersion > 0) { + console.log(`[Harness] Rolling back to version ${this.currentVersion - 1}`); + await this.rollbackToVersion(this.currentVersion - 1); + return { recovered: true, action: 'rollback', version: this.currentVersion }; + } + break; + + case 'timeout_error': + // For timeout errors, try with increased timeout + console.log(`[Harness] Retrying with increased timeout`); + const originalTimeout = this.config.timeout; + this.config.timeout *= 2; + + try { + const result = await this.retryOperation(() => this.update(context.lastState || {})); + return { recovered: true, action: 'timeout_increase', result }; + } finally { + this.config.timeout = originalTimeout; + } + break; + + case 'network_error': + // For network errors, implement exponential backoff + console.log(`[Harness] Implementing network error recovery`); + return await this.retryOperation( + () => this.update(context.lastState || {}), + { maxRetries: 5, delay: 2000, backoff: 2 } + ); + + default: + console.log(`[Harness] No specific recovery strategy for ${errorType}`); + break; + } + + return { recovered: false, error: error.message, errorType }; + } + + /** + * Enhanced state replay with error recovery + * + * @param {number} startVersion - Version to replay from + * @param {Object} newState - New state to apply + * @returns {Promise<Object>} Replay result + */ + async replayFromVersion(startVersion, newState) { + console.log(`[Harness] Replaying from version ${startVersion} with new state`); + + try { + // Get the state at the start version + const baseState = this.stateHistory.getVersion(startVersion); + if (!baseState) { + throw new Error(`Version ${startVersion} not found`); + } + + // Merge the base state with the new state + const mergedState = { ...baseState, ...newState }; + + // Replay the update with error recovery + const result = await this.retryOperation( + () => this.update(mergedState), + { maxRetries: 2, delay: 500 } + ); + + console.log(`[Harness] Replay completed successfully`); + return result; + + } catch (error) { + console.error(`[Harness] Replay failed:`, error.message); + return await this.recoverFromError(error, { lastState: newState }); + } } /** @@ -295,6 +495,7 @@ class FunctionalHarness { const scriptContent = this.scriptContent || await this.loadScriptFromFile(this.scriptPath); const initialState = this.translateToScript(environment.getCurrentState()); + // Call the run function from the imported module const result = interpreter.run(scriptContent, initialState, environment); clearTimeout(timeout); |