about summary refs log tree commit diff stats
path: root/js/scripting-lang/scripting-harness
diff options
context:
space:
mode:
Diffstat (limited to 'js/scripting-lang/scripting-harness')
-rw-r--r--js/scripting-lang/scripting-harness/core/harness.js263
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);