Task configuration:
{"task_id": "10", "task_name": "create_cone", "description": "Create a cone in FreeCAD", "application_info": {"name": "FreeCAD", "executable_path": "/usr/local/bin/FreeCAD", "args": []}, "task_parameters": {"source_path": "/FreeCAD/", "filename": "task10.FCStd", "radius": 10, "height": 30}, "instruction_template": "The application is already open. Create a cone in FreeCAD using 'create an additive cone' with base radius ${radius}mm and height ${height}mm, then save the file to ${source_path}${filename}", "evaluation_setup": {"scripts": [{"role": "hook", "path": "hooker.js"}, {"role": "handler", "path": "handler.py"}], "timeout": 240}, "preconditions": {"FreeCAD_running": true}, "total_key_steps": 2, "events": {"script_initialized": {"description": "Hook script initialized", "is_key_step": false}, "function_found": {"description": "Save document function found", "is_key_step": false}, "function_called": {"description": "Save document function called", "is_key_step": true, "key_step_index": 1, "key_step_name": "Save document"}, "function_key_word_detected": {"description": "Save document key content detected", "is_key_step": true, "key_step_index": 2, "key_step_name": "Cone created and saved successfully"}, "error": {"description": "Error event", "is_key_step": false}}, "mcp_servers": [{"command": "python3", "args": ["/home/agent/.local/share/FreeCAD/Mod/freecad_mcp/src/freecad_bridge.py"]}]}

Hook script:
// FreeCAD Cone Creation Monitoring Hook Script
// Used to monitor FreeCAD's cone creation operation and detect task completion
// After creating a cone and saving the file, the test program listens for the save and checks if there is a cone meeting the requirements in the corresponding document

(function() {
  // Script constant settings
  const FUNCTION_NAME = "_ZNK3App8Document10saveToFileEPKc"
  const ORIGIN_FUNCTION_NAME = "Document::saveToFile"
  const FUNCTION_BEHAVIOR = "Save Document"

  const SCRIPT_INITIALIZED = "script_initialized"
  const FUNCTION_NOT_FOUND = "function_not_found"
  const FUNCTION_FOUND = "function_found"
  const FUNCTION_CALLED = "function_called"
  const FUNCTION_KEY_WORD_DETECTED = "function_key_word_detected"
  const ERROR = "error"
  const HOOK_INSTALLED = "hook_installed"

  const APP_NAME = "FreeCAD"
  
  // Global variable
  let funcFound = false;
  
  // Send event to the evaluation system
  function sendEvent(eventType, data = {}) {
      const payload = {
          event: eventType,
          ...data,
          timestamp: new Date().getTime()
      };
      send(payload);
  }
  
  // Find Document::saveToFile function
  function getFunction() {
      // Try to find directly through exported symbols
      let FuncAddr = DebugSymbol.getFunctionByName(FUNCTION_NAME);
      
      // If not found, report an error
      if (!FuncAddr) {
          sendEvent(ERROR, {
              error_type: FUNCTION_NOT_FOUND,
              message: `Cannot find ${ORIGIN_FUNCTION_NAME} function`
          });
          return null;
      }
      
      // Report function found
      funcFound = true;
      sendEvent(FUNCTION_FOUND, {
          address: FuncAddr.toString(),
          message: `Found ${ORIGIN_FUNCTION_NAME} function`
      });
      
      return FuncAddr;
  }
  
  // Initialize hook and execute immediately
  function initHook() {
      sendEvent(SCRIPT_INITIALIZED, {
          message: `${APP_NAME} ${FUNCTION_BEHAVIOR} monitoring script started`
      });
      
      // Find search function
      const funcAddr = getFunction();
      if (!funcAddr) {
          return;
      }
      
      // Install search function hook
      Interceptor.attach(funcAddr, {
          onEnter: function(args) {
              try {
                  sendEvent(FUNCTION_CALLED, {
                      message: `Intercepted ${FUNCTION_BEHAVIOR} function call`
                  });
                  this.filename = args[1].readCString();
              } catch (error) {
                  sendEvent(ERROR, {
                      error_type: "general_error",
                      message: `Execution error: ${error.message}`
                  });
              }
          },

          onLeave: function(retval) {
              try {
                if (retval) {
                    const pythonCode = `
import freecad
import FreeCAD
import Part
import math

# Open the specified file
file_path = '${this.filename}'
doc = FreeCAD.open(file_path)

# Get active document
if doc is None:
    result = None
else:
    # Find cone in the document
    cone = None
    
    # Check all objects, look for cones
    for obj in doc.Objects:
        try:
            # Check if it's a Part::Feature or related type object
            if hasattr(obj, "TypeId") and "Part" in obj.TypeId:
                if hasattr(obj, "Shape") and hasattr(obj.Shape, "ShapeType"):
                    # Check if the object has a specific shape type
                    if obj.Shape.ShapeType == "Solid":
                        faces = obj.Shape.Faces
                        
                        # A cone created using additive cone typically has 2-3 faces: top face (small circle or none), bottom face (large circle), and side face
                        if len(faces) in [2, 3]:
                            # Look for one or two circular faces and one side face
                            circle_faces = []
                            side_face = None
                            
                            for face in faces:
                                # Check if it's a plane
                                if face.Surface.TypeId == 'Part::GeomPlane':
                                    # Check if it's circular
                                    is_circle = True
                                    radius = None
                                    for edge in face.Edges:
                                        if edge.Curve.TypeId == 'Part::GeomCircle':
                                            radius = edge.Curve.Radius
                                            break
                                        else:
                                            is_circle = False
                                            break
                                    
                                    if is_circle and radius is not None:
                                        # Record circle face center position and radius
                                        center = face.Surface.Position
                                        circle_faces.append({
                                            "radius": radius,
                                            "center": center
                                        })
                                else:
                                    # Might be a side face
                                    side_face = face
                            
                            # If we found one or two circular faces and one side face, it's likely a cone
                            if len(circle_faces) >= 1 and side_face is not None:
                                # Sort circular faces by z-coordinate, bottom face is lower, top face is higher (if exists)
                                circle_faces.sort(key=lambda x: x["center"].z)
                                bottom_face = circle_faces[0]
                                top_face = circle_faces[1] if len(circle_faces) > 1 else None
                                
                                # Calculate bottom radius, top radius (if exists), and height
                                bottom_radius = bottom_face["radius"]
                                top_radius = top_face["radius"] if top_face else 0
                                height = (top_face["center"].z - bottom_face["center"].z) if top_face else side_face.BoundBox.ZLength
                                
                                # If bottom radius is greater than top radius, consider it a cone (truncated or standard)
                                if bottom_radius > top_radius and height > 0:
                                    cone = {
                                        "radius": bottom_radius,
                                        "height": abs(height)
                                    }
                                    break
        except Exception as e:
            # Handle exception
            print(f"Error processing object {obj.Name}: {str(e)}")
            continue
    
    result = cone
                    `
                    sendEvent(FUNCTION_KEY_WORD_DETECTED, {
                        message: `Detected ${FUNCTION_BEHAVIOR} operation`,
                        filename: this.filename,
                        code: pythonCode
                    });
                }
              } catch (error) {
                  sendEvent(ERROR, {
                      error_type: "general_error",
                      message: `Execution error: ${error.message}`
                  });
              }
          }
      });
      
      sendEvent(HOOK_INSTALLED, {
          message: `Hook installed, waiting for ${FUNCTION_BEHAVIOR} operation...`
      });
  }
  
  // Execute hook initialization immediately
  initHook();
})();

Evaluation handler:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
FreeCAD Cone Event Handler
Handles events generated by the hook script and updates evaluation metrics
"""

from typing import Dict, Any, Optional, List

# Event type constants
SCRIPT_INITIALIZED = "script_initialized"
FUNCTION_NOT_FOUND = "function_not_found"
FUNCTION_FOUND = "function_found"
FUNCTION_CALLED = "function_called"
FUNCTION_KEY_WORD_DETECTED = "function_key_word_detected"
ERROR = "error"
HOOK_INSTALLED = "hook_installed"

# Keyword-related constants
RADIUS = "radius"
HEIGHT = "height"

def execute_python_code(code: str, logger: Any) -> Dict[str, Any]:
    """
    Execute Python code and return results
    
    Args:
        code: Python code to execute
        logger: Logger instance
        
    Returns:
        Dict[str, Any]: Execution results
    """
    try:
        # Create a new namespace to execute the code
        namespace = {}
        exec(code, namespace)
        result = namespace.get('result', None)
        
        if result is None:
            logger.warning("Cone object not found")
            return None
            
        # Validate result format
        required_keys = [RADIUS, HEIGHT]
        if not all(key in result for key in required_keys):
            logger.error(f"Result missing required keys: {required_keys}")
            return None
            
        return result
    except Exception as e:
        logger.error(f"Error executing Python code: {str(e)}")
        return None

def message_handler(message: Dict[str, Any], logger: Any, task_parameter: Dict[str, Any]) -> Optional[List[Dict[str, Any]]]:
    """
    Handle messages received from hook script
    
    Args:
        message: Frida message object
        logger: Logger instance
        task_parameter: Task parameters
        
    Returns:
        Optional[List[Dict[str, Any]]]: List of status updates, None if no updates
    """
    updates = []
    
    if message.get('type') == 'send' and 'payload' in message:
        payload = message['payload']
        
        if 'event' in payload:
            event_type = payload['event']
            logger.debug(f"Received event: {event_type}")
            
            if event_type == SCRIPT_INITIALIZED:
                logger.info(f"Hook script initialized: {payload.get('message', '')}")
                
            elif event_type == FUNCTION_FOUND:
                logger.info(f"Function found: {payload.get('address', '')}")
                
            elif event_type == FUNCTION_CALLED:
                logger.info(f"Function called: {payload.get('message', '')}")
                # Update first key step status
                updates.append({
                    'status': 'key_step',
                    'index': 1,
                    'name': 'Save document'
                })
                
            elif event_type == FUNCTION_KEY_WORD_DETECTED:
                # Execute Python code and get results
                code = payload.get('code', '')
                filename = payload.get('filename', '')
                expected_path = task_parameter.get("source_path", "") + task_parameter.get("filename", "")
                logger.info(f"Keyword detected, document path: {filename}, expected path: {expected_path}")
                
                if filename == expected_path:
                    result = execute_python_code(code, logger)
                    if result:
                        # Check if cone parameters match expectations
                        expected_radius = task_parameter.get(RADIUS, 10)
                        expected_height = task_parameter.get(HEIGHT, 30)

                        actual_radius = result[RADIUS]
                        actual_height = result[HEIGHT]

                        logger.info(f"Expected parameters: base radius={expected_radius}, height={expected_height}")
                        logger.info(f"Actual parameters: base radius={actual_radius}, height={actual_height}")

                        # Allow small error margin (0.01%)
                        radius_error = abs((actual_radius - expected_radius) / expected_radius) <= 0.0001
                        height_error = abs((actual_height - expected_height) / expected_height) <= 0.0001
                        
                        if radius_error and height_error:
                            # Update second key step status
                            updates.append({
                                'status': 'key_step',
                                'index': 2,
                                'name': 'Cone created and saved successfully'
                            })
                            
                            # Task completed successfully
                            updates.append({
                                'status': 'success',
                                'reason': 'Successfully created and saved a cone meeting requirements'
                            })
                        else:
                            logger.warning(f"Cone parameters do not meet requirements: radius correct: {radius_error}, height correct: {height_error}")
                
            elif event_type == ERROR:
                error_type = payload.get("error_type", "unknown")
                error_message = payload.get("message", "Unknown error")
                
                logger.error(f"Hook script error ({error_type}): {error_message}")
                
                # Record error event
                updates.append({
                    'status': 'error',
                    'type': error_type,
                    'message': error_message
                })
                
    elif message.get('type') == 'error':
        logger.error(f"Hook script error: {message.get('stack', '')}")
        
        # Record error event
        updates.append({
            'status': 'error',
            'type': 'script_error',
            'message': message.get('stack', 'Unknown error')
        })
    
    return updates if updates else None