Task configuration:
{"task_id": "13", "task_name": "create_disk_with_torus_cut", "description": "Create a disk with a toroidal groove in FreeCAD", "application_info": {"name": "FreeCAD", "executable_path": "/usr/local/bin/FreeCAD", "args": []}, "task_parameters": {"source_path": "/FreeCAD/", "filename": "task13.FCStd", "disk_radius": 30.0, "disk_height": 10.0, "torus_radius1": 20.0, "torus_radius2": 5.0, "torus_angle1": 0.0, "torus_angle2": 0.0, "torus_angle3": 360.0}, "instruction_template": "The application is already open. Create a disk in FreeCAD with radius ${disk_radius}mm and height ${disk_height}mm, then create a toroidal groove on its top surface. The toroidal groove's major radius (Radius1) is ${torus_radius1}mm, minor radius (Radius2) is ${torus_radius2}mm, with angle parameters Angle1=${torus_angle1} degrees, Angle2=${torus_angle2} degrees, and Angle3=${torus_angle3} degrees, defaulting to a complete toroidal groove. The groove depth on the disk equals the minor radius. The disk should be created using 'create an additive cylinder' (typeid), and the toroidal groove should be created using 'create subtractive torus' (typeid). Do not create the shapes separately and use boolean subtraction. Finally, save the file to ${source_path}${filename}", "evaluation_setup": {"scripts": [{"role": "hook", "path": "hooker.js"}, {"role": "handler", "path": "handler.py"}], "timeout": 300, "evaluator_type": "HookManager"}, "preconditions": {"freecad_running": false}, "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": "Disk with toroidal groove 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 Disk with Torus Groove Monitoring Hook Script
// Used to monitor the operation of creating a disk with a torus groove in FreeCAD and detect task completion
// After creating the disk with a torus groove, save the file. The test program listens for the save and queries the corresponding document to check if it contains the required disk with a torus groove

(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 variables
  let funcFound = false;

  // Send events to the evaluation system
  function sendEvent(eventType, data = {}) {
      const payload = {
          event: eventType,
          ...data,
          timestamp: new Date().getTime()
      };
      send(payload);
  }

  // Find the Document::saveToFile function
  function getFunction() {
      // Attempt to find it directly via exported symbols
      let FuncAddr = DebugSymbol.getFunctionByName(FUNCTION_NAME);

      // If not found, report an error
      if (!FuncAddr) {
          sendEvent(ERROR, {
              error_type: FUNCTION_NOT_FOUND,
              message: `Unable to find the ${ORIGIN_FUNCTION_NAME} function`
          });
          return null;
      }

      // Report the function was found
      funcFound = true;
      sendEvent(FUNCTION_FOUND, {
          address: FuncAddr.toString(),
          message: `Found the ${ORIGIN_FUNCTION_NAME} function`
      });

      return FuncAddr;
  }

  // Initialize the hook and execute immediately
  function initHook() {
      sendEvent(SCRIPT_INITIALIZED, {
          message: `${APP_NAME} ${FUNCTION_BEHAVIOR} monitoring script has started`
      });

      // Find the target function
      const funcAddr = getFunction();
      if (!funcAddr) {
          return;
      }

      // Install the hook for the target function
      Interceptor.attach(funcAddr, {
          onEnter: function(args) {
              try {
                  sendEvent(FUNCTION_CALLED, {
                      message: `Intercepted a call to the ${FUNCTION_BEHAVIOR} function`
                  });
                  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 the active document
if doc is None:
    result = None
else:
    # Find the disk and torus groove
    disk = None
    torus_cut = None

    # Check all objects to find the disk (cylinder) and torus groove (torus)
    for obj in doc.Objects:
        # Check if it is a solid object
        if hasattr(obj, "Shape"):
            # Check the shape type
            if hasattr(obj.Shape, "ShapeType"):
                # For objects created using Part Design, check sub-objects
                if obj.TypeId == "PartDesign::Body":
                    # Prevent duplicate counting of the same object
                    processed_objects = set()

                    for subobj in obj.OutList:
                        if hasattr(subobj, "Shape") and hasattr(subobj.Shape, "ShapeType"):
                            # Check if it is an additive cylinder (disk) using TypeId
                            if subobj.TypeId == 'PartDesign::AdditiveCylinder':
                                # Use object ID as a unique identifier
                                obj_id = subobj.ID if hasattr(subobj, "ID") else subobj.Name

                                # Skip if this object has already been processed
                                if obj_id in processed_objects:
                                    continue
                                processed_objects.add(obj_id)

                                if subobj.Shape.ShapeType == "Solid" and hasattr(subobj.Shape, "Volume"):
                                    # Check if it is a disk and directly read its properties
                                    disk_radius = subobj.Radius.Value if hasattr(subobj, "Radius") else 0
                                    disk_height = subobj.Height.Value if hasattr(subobj, "Height") else 0

                                    # Store disk information
                                    disk = {
                                        'radius': disk_radius,
                                        'height': disk_height
                                    }

                            # Check if it is a subtractive torus (torus groove) using TypeId
                            elif subobj.TypeId == 'PartDesign::SubtractiveTorus':
                                # Use object ID as a unique identifier
                                obj_id = subobj.ID if hasattr(subobj, "ID") else subobj.Name

                                # Skip if this object has already been processed
                                if obj_id in processed_objects:
                                    continue
                                processed_objects.add(obj_id)

                                if subobj.Shape.ShapeType == "Solid" and hasattr(subobj.Shape, "Volume"):
                                    # Directly get torus properties from the object
                                    torus_radius1 = subobj.Radius1.Value if hasattr(subobj, "Radius1") else 0
                                    torus_radius2 = subobj.Radius2.Value if hasattr(subobj, "Radius2") else 0
                                    torus_angle1 = subobj.Angle1 if hasattr(subobj, "Angle1") else 0
                                    torus_angle2 = subobj.Angle2 if hasattr(subobj, "Angle2") else 0
                                    torus_angle3 = subobj.Angle3 if hasattr(subobj, "Angle3") else 360

                                    # Store torus groove information
                                    torus_cut = {
                                        'radius1': torus_radius1,
                                        'radius2': torus_radius2,
                                        'angle1': torus_angle1,
                                        'angle2': torus_angle2,
                                        'angle3': torus_angle3
                                    }

    # Return the result - ensure pure numeric values without units
    # Handle values that may have units
    def extract_value(val):
        if val is None:
            return None
        try:
            # If it is a string with units (e.g., "10.0 mm"), extract the numeric part
            if isinstance(val, str) and ' ' in val:
                return float(val.split()[0])
            # If it is a FreeCAD Quantity object, try converting to float
            return float(val)
        except:
            # If conversion fails, return the original value
            return val

    result = {
    'disk_radius': extract_value(disk['radius']) if disk else None,
    'disk_height': extract_value(disk['height']) if disk else None,
    'torus_radius1': extract_value(torus_cut['radius1']) if torus_cut else None,
    'torus_radius2': extract_value(torus_cut['radius2']) if torus_cut else None,
    'torus_angle1': extract_value(torus_cut['angle1']) if torus_cut else None,
    'torus_angle2': extract_value(torus_cut['angle2']) if torus_cut else None,
    'torus_angle3': extract_value(torus_cut['angle3']) if torus_cut else None,
    'has_torus_cut': torus_cut is not None
    }
                    `
                    sendEvent(FUNCTION_KEY_WORD_DETECTED, {
                        message: `Detected ${FUNCTION_BEHAVIOR} operation`,
                        filename: this.filename,
                        code: pythonCode
                    });
                }
                // Detect keywords
              } 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 Disk with Torus Groove Event Handler
Responsible for handling events generated by the hook script and updating 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
DISK_RADIUS = "disk_radius"
DISK_HEIGHT = "disk_height"
TORUS_RADIUS1 = "torus_radius1"
TORUS_RADIUS2 = "torus_radius2"
TORUS_ANGLE1 = "torus_angle1"
TORUS_ANGLE2 = "torus_angle2"
TORUS_ANGLE3 = "torus_angle3"
HAS_TORUS_CUT = "has_torus_cut"

def execute_python_code(code: str, logger: Any) -> Dict[str, Any]:
    """
    Execute Python code and return the result

    Args:
        code: Python code to execute
        logger: Logger instance

    Returns:
        Dict[str, Any]: Execution result
    """
    try:
        # Create a new namespace to execute the code
        namespace = {}
        exec(code, namespace)
        result = namespace.get('result', None)

        if result is None:
            logger.warning("Disk with torus groove object not found")
            return None

        # Validate result format
        required_keys = [
            DISK_RADIUS, DISK_HEIGHT, 
            TORUS_RADIUS1, TORUS_RADIUS2, 
            TORUS_ANGLE1, TORUS_ANGLE2, 
            TORUS_ANGLE3, HAS_TORUS_CUT
        ]
        if not all(key in result for key in required_keys):
            logger.error(f"Result is 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 the hook script

    Args:
        message: Frida message object
        logger: Logger instance
        task_parameter: Task parameters

    Returns:
        Optional[List[Dict[str, Any]]]: List of status updates, or 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 the status of the first key step
                updates.append({
                    'status': 'key_step',
                    'index': 1,
                    'name': 'Save Document'
                })

            elif event_type == FUNCTION_KEY_WORD_DETECTED:
                # Execute Python code and get the result
                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 document path: {expected_path}")

                if filename == expected_path:
                    result = execute_python_code(code, logger)
                    if result:
                        # Check if the disk with torus groove parameters meet expectations
                        expected_disk_radius = task_parameter.get(DISK_RADIUS, 30.0)
                        expected_disk_height = task_parameter.get(DISK_HEIGHT, 10.0)
                        expected_torus_radius1 = task_parameter.get(TORUS_RADIUS1, 20.0)
                        expected_torus_radius2 = task_parameter.get(TORUS_RADIUS2, 5.0)
                        expected_torus_angle1 = task_parameter.get(TORUS_ANGLE1, 0.0)
                        expected_torus_angle2 = task_parameter.get(TORUS_ANGLE2, 0.0)
                        expected_torus_angle3 = task_parameter.get(TORUS_ANGLE3, 360.0)

                        actual_disk_radius = result[DISK_RADIUS]
                        actual_disk_height = result[DISK_HEIGHT]
                        actual_torus_radius1 = result[TORUS_RADIUS1]
                        actual_torus_radius2 = result[TORUS_RADIUS2]
                        actual_torus_angle1 = result[TORUS_ANGLE1]
                        actual_torus_angle2 = result[TORUS_ANGLE2]
                        actual_torus_angle3 = result[TORUS_ANGLE3]
                        actual_has_torus_cut = result[HAS_TORUS_CUT]

                        # Log key parameters
                        logger.info(f"Task parameter check: Expected disk radius={expected_disk_radius}, height={expected_disk_height}")
                        logger.info(f"Expected torus groove: Radius1={expected_torus_radius1}, Radius2={expected_torus_radius2}, " +
                                   f"Angle1={expected_torus_angle1}, Angle2={expected_torus_angle2}, " +
                                   f"Angle3={expected_torus_angle3}")
                        logger.info(f"Actual parameters: Actual disk radius={actual_disk_radius}, height={actual_disk_height}")
                        logger.info(f"Actual torus groove: Radius1={actual_torus_radius1}, Radius2={actual_torus_radius2}, " +
                                   f"Angle1={actual_torus_angle1}, Angle2={actual_torus_angle2}, " +
                                   f"Angle3={actual_torus_angle3}, " +
                                   f"Has torus groove: {actual_has_torus_cut}")

                        # Convert values with possible units to floats
                        try:
                            # Attempt to extract numeric part (handle possible units)
                            actual_disk_radius_value = float(str(actual_disk_radius).split()[0]) if isinstance(actual_disk_radius, str) else float(actual_disk_radius)
                            actual_disk_height_value = float(str(actual_disk_height).split()[0]) if isinstance(actual_disk_height, str) else float(actual_disk_height)

                            actual_torus_radius1_value = float(str(actual_torus_radius1).split()[0]) if isinstance(actual_torus_radius1, str) else float(actual_torus_radius1)
                            actual_torus_radius2_value = float(str(actual_torus_radius2).split()[0]) if isinstance(actual_torus_radius2, str) else float(actual_torus_radius2)
                            actual_torus_angle1_value = float(str(actual_torus_angle1).split()[0]) if isinstance(actual_torus_angle1, str) else float(actual_torus_angle1)
                            actual_torus_angle2_value = float(str(actual_torus_angle2).split()[0]) if isinstance(actual_torus_angle2, str) else float(actual_torus_angle2)
                            actual_torus_angle3_value = float(str(actual_torus_angle3).split()[0]) if isinstance(actual_torus_angle3, str) else float(actual_torus_angle3)

                            # Allow a certain margin of error (0.01%)
                            disk_radius_error = abs((actual_disk_radius_value - expected_disk_radius) / expected_disk_radius) <= 0.0001
                            disk_height_error = abs((actual_disk_height_value - expected_disk_height) / expected_disk_height) <= 0.0001

                            torus_radius1_error = abs((actual_torus_radius1_value - expected_torus_radius1) / expected_torus_radius1) <= 0.0001
                            torus_radius2_error = abs((actual_torus_radius2_value - expected_torus_radius2) / expected_torus_radius2) <= 0.0001

                            # Angles may have multiple representations, allow larger error
                            torus_angle1_error = abs(actual_torus_angle1_value - expected_torus_angle1) <= 1.0
                            torus_angle2_error = abs(actual_torus_angle2_value - expected_torus_angle2) <= 1.0
                            torus_angle3_error = abs(actual_torus_angle3_value - expected_torus_angle3) <= 1.0

                            has_torus_cut_correct = actual_has_torus_cut
                        except Exception as e:
                            logger.error(f"Error comparing parameters: {str(e)}")
                            disk_radius_error = False
                            disk_height_error = False
                            torus_radius1_error = False
                            torus_radius2_error = False
                            torus_angle1_error = False
                            torus_angle2_error = False
                            torus_angle3_error = False
                            has_torus_cut_correct = False

                        disk_correct = disk_radius_error and disk_height_error
                        torus_correct = (torus_radius1_error and torus_radius2_error and
                                        torus_angle1_error and torus_angle2_error and
                                        torus_angle3_error and has_torus_cut_correct)

                        if disk_correct and torus_correct:
                            # Update the status of the second key step
                            updates.append({
                                'status': 'key_step',
                                'index': 2,
                                'name': 'Create disk with torus groove and save successfully'
                            })

                            # Task successfully completed
                            updates.append({
                                'status': 'success',
                                'reason': 'Successfully created a disk with torus groove that meets the requirements and saved it'
                            })
                        else:
                            logger.warning(f"Parameter validation failed: " + 
                                          f"Disk correct: {disk_correct}, " +
                                          f"Torus groove correct: {torus_correct}")

            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