Task configuration:
{"task_id": "15", "task_name": "add_font_source", "description": "Add a new text source and modify some of its settings", "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", "new_source_name": "test", "settings": {"font": {"face": "Ubuntu", "style": "Regular", "size": 256, "flags": 0}, "text": "one two three"}}, "instruction_template": "Please add a new text source in the scene ${scene_name} in obs, named ${new_source_name}, with settings ${settings}", "evaluation_setup": {"scripts": [{"role": "hook", "path": "hooker.js"}, {"role": "handler", "path": "handler.py"}], "timeout": 180}, "preconditions": {"obs_studio_running": true}, "total_key_steps": 3, "events": {"found_scene": {"description": "Created new scene successfully", "is_key_step": true, "key_step_index": 1, "key_step_name": "Created new scene successfully"}, "found_text_source": {"description": "Created new text source successfully", "is_key_step": true, "key_step_index": 2, "key_step_name": "Created new text source successfully"}, "settings_matched": {"description": "Text source properties matched successfully", "is_key_step": true, "key_step_index": 3, "key_step_name": "Text source properties matched successfully"}}, "mcp_servers": [{"command": "node", "args": ["/home/agent/obs-mcp/build/index.js"]}]}

Hook script:
(function () {
    // Script settings
    const EVENT_ON_ENTER = "function called";
    const EVENT_ON_LEAVE = "function returned";

    const MESSAGE_called = "Intercepted function call";
    const MESSAGE_returned = "Function returned";
    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: `Cannot find function ${functionName}`
            });
            return null;
        }

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

    // Initialize recording update hooks
    function hook() {
        let function_name = "OBSBasic::Save";
        let symbol_name = "_ZN8OBSBasic4SaveEPKc";

        Interceptor.attach(getFunctionAddress(symbol_name), {
            onEnter(args) {
                sendEvent(EVENT_ON_ENTER, {
                    message: MESSAGE_called,
                    function: function_name,
                    symbol: symbol_name
                });
                this.file = args[1].readCString(-1);
            },

            onLeave(retval) {
                sendEvent(EVENT_ON_LEAVE, {
                    message: MESSAGE_returned,
                    function: function_name,
                    symbol: symbol_name,
                    file: this.file
                });
            }
        });
    }

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

        // Initialize individual hooks
        hook();
        sendEvent("hook_installed", {
            message: MESSAGE_hook_installed
        });
    }

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

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

import os
import json
from typing import Dict, Any, Optional, List

_EVENT_FUNCTION_RETURN = "function returned"


def message_handler(
    message: Dict[str, Any], logger, task_parameter: Dict[str, Any]
) -> Optional[List[Dict[str, Any]]]:
    payload = message["payload"]
    event_type = payload["event"]
    logger.debug(f"Received event: {event_type}")
    key_step = []
    if event_type == _EVENT_FUNCTION_RETURN:
        function = payload.get("function", "")
        logger.info("Function returned: " + function)
        if function == "OBSBasic::Save":
            file = payload.get("file", "")
            logger.info("Scene file: " + file)
            if file and os.path.exists(file):
                with open(file, "r") as f:
                    scene_data = json.load(f)
                    sources = scene_data.get("sources", [])
                    scene_name = task_parameter.get("scene_name", "")
                    # Check if the scene name exists
                    if scene_name:
                        for source in sources:
                            if source.get("name") == scene_name:
                                key_step.append({"status": "key_step", "index": 1})
                                logger.info(
                                    "key step 1: Scene exists in the scene file"
                                )
                                # Check if the text source name exists in the items list of the settings dictionary in the scene
                                items = source.get("settings", {}).get("items", [])
                                for item in items:
                                    if item.get("name") == task_parameter.get(
                                        "new_source_name", ""
                                    ):
                                        key_step.append(
                                            {"status": "key_step", "index": 2}
                                        )
                                        logger.info(
                                            "key step 2: Text source exists in the scene file"
                                        )
                                        break
                                else:
                                    logger.error(
                                        f"Text source does not exist in scene {scene_name}"
                                    )
                                    key_step.append(
                                        {
                                            "status": "error",
                                            "message": f"Text source does not exist in scene {scene_name}",
                                        }
                                    )
                                break
                        else:
                            logger.error(f"Scene {scene_name} does not exist")
                            key_step.append(
                                {
                                    "status": "error",
                                    "message": f"Scene {scene_name} does not exist",
                                }
                            )
                    else:
                        logger.error("Scene name not provided")
                        key_step.append(
                            {"status": "error", "message": "Scene name not provided"}
                        )
                    # Check if the text source name exists
                    name = task_parameter.get("new_source_name", "")
                    for source in sources:
                        if source.get("name") == name:
                            source_settings = source.get("settings", {})
                            expected_settings = task_parameter.get("settings", {})
                            # Check if all settings in expected_settings exist in source_settings and match their values
                            is_match = True
                            for key, expected_value in expected_settings.items():
                                if key not in source_settings:
                                    is_match = False
                                    logger.error(f"Setting {key} does not exist")
                                    break
                                if source_settings[key] != expected_value:
                                    is_match = False
                                    logger.error(
                                        f"Setting {key} value does not match: expected {expected_value}, actual {source_settings[key]}"
                                    )
                                    break

                            if is_match:
                                key_step.append({"status": "key_step", "index": 3})
                                logger.info(
                                    "key step 3: Settings match in the scene file"
                                )
                                key_step.append(
                                    {
                                        "status": "success",
                                        "reason": "Scene and text source match, settings successful",
                                    }
                                )
                                logger.info(
                                    f"Settings for text source {name} matched successfully"
                                )
                            else:
                                logger.error(
                                    f"Settings for text source {name} do not match"
                                )
                                logger.debug(f"Expected settings: {expected_settings}")
                                logger.debug(f"Actual settings: {source_settings}")

    return key_step