Task configuration:
{"task_id": "19", "task_name": "export_scene_screenshot", "description": "obs-studio export scene screenshot", "application_info": {"name": "obs-studio", "executable_path": "/workspace/PC-Canary/apps/obs-studio/build_ubuntu/rundir/Debug/bin/obs", "args": []}, "task_parameters": {"scene_name": "Scene", "save_path": "/home/agent/scene_screenshot.png", "width": 1024, "height": 768}, "instruction_template": "Please export a screenshot of scene ${scene_name} in obs-studio, save it to ${save_path}, with resolution ${width}x${height}", "evaluation_setup": {"scripts": [{"role": "hook", "path": "hooker.js"}, {"role": "handler", "path": "handler.py"}], "timeout": 180}, "preconditions": {"obs_studio_running": true}, "total_key_steps": 2, "events": {"path_match": {"description": "Screenshot path matched", "is_key_step": true, "key_step_index": 1, "key_step_name": "Screenshot path matched"}, "size_match": {"description": "Size matched", "is_key_step": true, "key_step_index": 2, "key_step_name": "Size matched"}}, "mcp_servers": [{"command": "node", "args": ["/home/agent/obs-mcp/build/index.js"]}]}

Hook script:
(function () {
    // Script settings
    const FUNCTION_MUXANDFINISH = "ScreenshotObj::MuxAndFinish";
    const FUNCTION_SCREENSHOT = "OBSBasic::Screenshot";
    const FUNCTION_GETOUTPUTFILENAME = "GetOutputFilename";
    const SYMBOL_MUXANDFINISH = "_ZN13ScreenshotObj12MuxAndFinishEv";
    const SYMBOL_SCREENSHOT = "_ZN8OBSBasic10ScreenshotE10OBSSafeRefIP10obs_sourceXadL_Z18obs_source_get_refEEXadL_Z18obs_source_releaseEEE";
    const SYMBOL_GETOUTPUTFILENAME = "_Z17GetOutputFilenameB5cxx11PKcS0_bbS0_";

    const EVENT_ON_ENTER = "function called";
    const EVENT_ON_LEAVE = "function returned";
    const EVENT_GET_PATH = "screenshot_getpath";
    const EVENT_ON_SUCCESS = "screenshot_saved";
    const EVENT_ON_ERROR = "screenshot_error";
    
    const MESSAGE_screenshot_called = "Intercepted screenshot function call";
    const MESSAGE_screenshot_returned = "Screenshot function returned";
    const MESSAGE_getfilename_called = "Intercepted get filename function call";
    const MESSAGE_getfilename_returned = "Get filename function returned";
    const MESSAGE_ON_SUCCESS = "Screenshot saved successfully";
    const MESSAGE_ON_ERROR = "Screenshot save failed";
    const MESSAGE_script_initialized = "Monitoring script has started";
    const MESSAGE_hook_installed = "Monitoring hook installed, waiting for operation...";

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

    // Get function address
    function getFunctionAddress(functionName) {
        const funcAddr = DebugSymbol.getFunctionByName(functionName);
        if (!funcAddr) {
            sendEvent("error", {
                error_type: "function_not_found",
                message: `Unable to find function ${functionName}`
            });
            return null;
        }

        sendEvent("function_found", {
            address: funcAddr.toString(),
            message: `Found actual address of function ${functionName}`
        });
        return funcAddr;
    }

    // Initialize screenshot hook
    function initHook_screenshot() {
        const funcAddr = getFunctionAddress(SYMBOL_SCREENSHOT);
        if (!funcAddr) {
            return;
        }

        Interceptor.attach(funcAddr, {
            onEnter(args) {
                try {
                    sendEvent(EVENT_ON_ENTER, {
                        message: MESSAGE_screenshot_called,
                        function: FUNCTION_SCREENSHOT,
                        symbol: SYMBOL_SCREENSHOT
                    });
                    
                    // Save source parameter for later use
                    this.source = args[1];
                } catch (error) {
                    sendEvent(EVENT_ON_ERROR, {
                        message: "Failed to retrieve screenshot function call parameters",
                        error: error.toString()
                    });
                }
            },
            
            onLeave(retval) {
                try {
                    sendEvent(EVENT_ON_LEAVE, {
                        message: MESSAGE_screenshot_returned,
                        function: FUNCTION_SCREENSHOT,
                        symbol: SYMBOL_SCREENSHOT,
                        success: retval.toInt32() !== 0
                    });
                } catch (error) {
                    sendEvent(EVENT_ON_ERROR, {
                        message: "Failed to retrieve screenshot function return value",
                        error: error.toString()
                    });
                }
            }
        });
    }

    // Initialize get filename hook
    function initHook_getOutputFilename() {
        const funcAddr = getFunctionAddress(SYMBOL_GETOUTPUTFILENAME);
        if (!funcAddr) {
            return;
        }

        Interceptor.attach(funcAddr, {
            onEnter(args) {
                console.log("call getoutputfilename");
                try {
                    sendEvent(EVENT_ON_ENTER, {
                        message: MESSAGE_getfilename_called,
                        function: FUNCTION_GETOUTPUTFILENAME,
                        symbol: SYMBOL_GETOUTPUTFILENAME
                    });
                    
                    // Save parameters for onLeave use
                    this.path = args[0].readCString();
                    this.container = args[1].readCString();
                    this.noSpace = args[2].toInt32();
                    this.overwrite = args[3].toInt32();
                    this.format = args[4].readCString();
                } catch (error) {
                    sendEvent(EVENT_ON_ERROR, {
                        message: "Failed to retrieve get filename function parameters",
                        error: error.toString()
                    });
                }
            },
            
            onLeave(retval) {
                try {
                    // Retrieve returned file path
                    const filePath = retval.readPointer().readCString();
                    console.log(filePath);
                    
                    sendEvent(EVENT_ON_LEAVE, {
                        message: MESSAGE_getfilename_returned,
                        function: FUNCTION_GETOUTPUTFILENAME,
                        symbol: SYMBOL_GETOUTPUTFILENAME,
                        path: this.path,
                        container: this.container,
                        noSpace: this.noSpace,
                        overwrite: this.overwrite,
                        format: this.format,
                        filePath: filePath
                    });
                    
                    // Send screenshot save path event
                    sendEvent(EVENT_GET_PATH, {
                        message: "Obtained saved path",
                        save_path: filePath
                    });
                } catch (error) {
                    sendEvent(EVENT_ON_ERROR, {
                        message: "Failed to retrieve get filename function return value",
                        error: error.toString()
                    });
                }
            }
        });
    }

    function initHook_muxAndFinish() {
        const funcAddr = getFunctionAddress(SYMBOL_MUXANDFINISH);
        if (!funcAddr) {
            return;
        }

        Interceptor.attach(funcAddr, {
            onEnter(args) {
                console.log("call muxandfinish");
                try {
                    sendEvent(EVENT_ON_ENTER, {
                        message: "muxAndFinish called",
                        function: FUNCTION_MUXANDFINISH,
                        symbol: SYMBOL_MUXANDFINISH
                    });
                } catch (error) {
                    sendEvent(EVENT_ON_ERROR, {
                        message: "muxAndFinish function call failed",
                        error: error.toString()
                    });
                }
            },
            onLeave(retval) {
                try {
                    sendEvent(EVENT_ON_LEAVE, {
                        message: "muxAndFinish exited",
                        function: FUNCTION_MUXANDFINISH,
                        symbol: SYMBOL_MUXANDFINISH
                    });
                    sendEvent(EVENT_ON_SUCCESS, {
                        message: MESSAGE_ON_SUCCESS
                    });
                } catch (error) {
                    sendEvent(EVENT_ON_ERROR, {
                        message: "muxAndFinish function return handling failed",
                        error: error.toString()
                    });
                }
            }
        });
    }

    function initRequestHandlerSaveScreenshotHook() {
        const funcAddr = getFunctionAddress("_ZN14RequestHandler20SaveSourceScreenshotERK7Request");
        if (!funcAddr) {
            return;
        }

        Interceptor.attach(funcAddr, {
            onEnter(args) {
                console.log("call RequestHandler");
            },
            onLeave(retval) {
                console.log("leave RequestHandler");
                sendEvent("RequestHandlerSaveScreenshot_returned", {
                    message: MESSAGE_ON_SUCCESS
                });
            }
        });
    }

    // Initialize hooks
    function initHook() {
        sendEvent("script_initialized", {
            message: MESSAGE_script_initialized
        });

        // Initialize hooks
        initHook_screenshot();
        initHook_getOutputFilename();
        initHook_muxAndFinish();
        initRequestHandlerSaveScreenshotHook();
        
        sendEvent("hook_installed", {
            message: MESSAGE_hook_installed
        });
    }

    // Start script
    initHook();
})();

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

import os
from typing import Dict, Any, Optional, List
from PIL import Image

save_path = None
screenshot_called = False
screenshot_success = False


def verify_screenshot_path(
    save_path: str, logger, task_parameter
) -> List[Dict[str, Any]]:
    """Verify if the screenshot save path meets the requirements"""
    try:
        expected_path = task_parameter["save_path"]
        print(os.path.dirname(save_path))
        print(expected_path)
        # Check if the path matches
        if expected_path.rstrip("/") != os.path.dirname(save_path):
            logger.error(
                f"Screenshot save path does not match: Expected {expected_path}, Actual {save_path}"
            )
            return [
                {
                    "status": "error",
                    "message": f"Screenshot save path does not match: Expected {expected_path}, Actual {save_path}",
                }
            ]

        # Check if the file exists
        if not os.path.exists(save_path):
            logger.error(f"Screenshot file does not exist: {save_path}")
            return [
                {
                    "status": "error",
                    "message": f"Screenshot file does not exist: {save_path}",
                }
            ]

        return [{"status": "key_step", "index": 1}]
    except Exception as e:
        logger.error(f"Error occurred while verifying screenshot path: {e}")
        return [
            {
                "status": "error",
                "message": f"Error occurred while verifying screenshot path: {e}",
            }
        ]


def verify_screenshot_size(
    save_path: str, logger, task_parameter
) -> List[Dict[str, Any]]:
    """Verify if the screenshot size meets the requirements"""
    try:
        with Image.open(save_path) as img:
            width, height = img.size
            expected_width = task_parameter["width"]
            expected_height = task_parameter["height"]

            if width != expected_width or height != expected_height:
                logger.error(
                    f"Screenshot size does not match: Expected {expected_width}x{expected_height}, "
                    f"Actual {width}x{height}"
                )
                return [
                    {
                        "status": "error",
                        "message": f"Screenshot size does not match: Expected {expected_width}x{expected_height}, Actual {width}x{height}",
                    }
                ]

            return [
                {"status": "key_step", "index": 2},
                {
                    "status": "success",
                    "reason": "Screenshot path and size both match successfully",
                },
            ]
    except Exception as e:
        logger.error(f"Error occurred while verifying screenshot size: {e}")
        return [
            {
                "status": "error",
                "message": f"Error occurred while verifying screenshot size: {e}",
            }
        ]


def message_handler(
    message: Dict[str, Any], logger, task_parameter: Dict[str, Any]
) -> Optional[List[Dict[str, Any]]]:
    global save_path, screenshot_called, screenshot_success
    payload = message["payload"]
    print(payload)
    event = payload["event"]
    logger.debug(f"Received event: {event}")
    key_step = []

    # Handle function call event
    if event == "function called" and payload.get("function") == "OBSBasic::Screenshot":
        screenshot_called = True
        logger.info("Detected screenshot function call")
        return None

    # Handle function return event
    if (
        event == "function returned"
        and payload.get("function") == "OBSBasic::Screenshot"
    ):
        screenshot_success = payload.get("success", False)
        if not screenshot_success:
            logger.error("Screenshot function execution failed")
        return None

    # Handle event to get save path
    if event == "screenshot_getpath":
        save_path = payload.get("save_path")
        if not save_path:
            logger.error("Failed to get screenshot save path")
            return None
        logger.info(f"Screenshot save path obtained: {save_path}")

    # Handle error event
    if event == "screenshot_error":
        error_msg = payload.get("message", "Unknown error")
        error_detail = payload.get("error", "No detailed information")
        logger.error(
            f"Error occurred during screenshot process: {error_msg}, Details: {error_detail}"
        )
        return [{"status": "error", "message": f"{error_msg}"}]

    # Handle screenshot save success event
    if event == "screenshot_saved":
        if not screenshot_called:
            logger.error("Screenshot function call not detected")
            return None

        if not screenshot_success:
            logger.error("Screenshot function execution failed")
            return None

        if not save_path:
            logger.error("Failed to get screenshot save path")
            return None

        # Verify screenshot path and size
        key_step.extend(verify_screenshot_path(save_path, logger, task_parameter))
        key_step.extend(verify_screenshot_size(save_path, logger, task_parameter))

        return key_step

    if event == "RequestHandlerSaveScreenshot_returned":
        # Check if there is a valid .png file in the save_path folder set in config.json
        config_save_path = task_parameter.get("save_path")
        if not config_save_path:
            logger.error("Save path not found in configuration")
            return [
                {"status": "error", "message": "Save path not found in configuration"}
            ]

        try:
            file_path = config_save_path
            if not os.path.exists(file_path):
                logger.error(f"File {file_path} does not exist")
                return [
                    {"status": "error", "message": f"File {file_path} does not exist"}
                ]

            with Image.open(file_path) as img:
                width, height = img.size
                expected_width = task_parameter["width"]
                expected_height = task_parameter["height"]

                if width != expected_width or height != expected_height:
                    logger.error(
                        f"Resolution of file {file_path} does not match: "
                        f"Expected {expected_width}x{expected_height}, Actual {width}x{height}"
                    )
                    return [
                        {
                            "status": "error",
                            "message": f"Resolution of file {file_path} does not match: Expected {expected_width}x{expected_height}, Actual {width}x{height}",
                        }
                    ]

            logger.info(f"File {file_path} verified successfully")
            return [
                {"status": "key_step", "index": 1},
                {"status": "key_step", "index": 2},
                {
                    "status": "success",
                    "message": f"File {file_path} verified successfully",
                },
            ]
        except Exception as e:
            logger.error(f"Error occurred while checking file: {e}")
            return [
                {
                    "status": "error",
                    "message": f"Error occurred while checking file: {e}",
                }
            ]

    return None