Task configuration:
{"task_id": "22", "task_name": "configure_recording_output", "description": "Configure recording output path and format, and test recording", "application_info": {"name": "obs-studio", "executable_path": "/workspace/PC-Canary/apps/obs-studio/build_ubuntu/rundir/Debug/bin/obs", "args": []}, "task_parameters": {"output_path": "/tmp/obs-recordings", "output_format": "mp4"}, "instruction_template": "Please configure the recording output path as ${output_path} in obs-studio, with format ${output_format}, and then test the recording function", "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": {"output_path_configured": {"description": "Recording output path configured", "is_key_step": true, "key_step_index": 1, "key_step_name": "Recording output path configured successfully"}, "output_format_configured": {"description": "Recording output format configured", "is_key_step": true, "key_step_index": 2, "key_step_name": "Recording output format configured successfully"}, "recording_tested": {"description": "Recording function tested", "is_key_step": true, "key_step_index": 3, "key_step_name": "Recording function tested successfully"}}, "mcp_servers": [{"command": "node", "args": ["/home/agent/obs-mcp/build/index.js"]}]}

Hook script:
// OBS recording output configuration and test monitoring hook script
// Used to monitor OBS recording output path and format configuration, as well as recording test operations

(function () {
    // Script settings
    const FUNCTION_ConfigureRecording = "_ZN12SimpleOutput18ConfigureRecordingEb";
    const FUNCTION_StartRecording = "_ZN12SimpleOutput14StartRecordingEv";
    const FUNCTION_StopRecording = "_ZN12SimpleOutput13StopRecordingEb";
    // const FUNCTION_OBSBasic_GetCurrentOutputPath = "_ZN8OBSBasic20GetCurrentOutputPathEv";
    const FUNCTION_OBSBasicSettings_SaveOutputSettings = "_ZN16OBSBasicSettings18SaveOutputSettingsEv";

    // 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 configure recording output hook
    function initConfigureRecordingHook() {
        const configureRecordingFuncAddr = getFunctionAddress(FUNCTION_ConfigureRecording);
        if (!configureRecordingFuncAddr) {
            return;
        }

        Interceptor.attach(configureRecordingFuncAddr, {
            onEnter: function(args) {
                this.updateReplayBuffer = args[1].toInt32();
                sendEvent("configure_recording_called", {
                    message: "Intercepted configure recording output function call",
                    updateReplayBuffer: this.updateReplayBuffer
                });
            },

            onLeave: function(retval) {
                sendEvent("configure_recording_returned", {
                    message: "Configure recording output function returned",
                    result: retval.toInt32() !== 0
                });
            }
        });
    }

    // Initialize save output settings hook
    function initSaveOutputSettingsHook() {
        const saveOutputSettingsFuncAddr = getFunctionAddress(FUNCTION_OBSBasicSettings_SaveOutputSettings);
        if (!saveOutputSettingsFuncAddr) {
            return;
        }

        Interceptor.attach(saveOutputSettingsFuncAddr, {
            onEnter: function(args) {
                // Save this pointer for access in onLeave
                this.settingsThis = args[0];
                sendEvent("save_output_settings_called", {
                    message: "Intercepted save output settings function call"
                });
            },

            onLeave: function(retval) {
                sendEvent("save_output_settings_returned", {
                    message: "Save output settings function returned"
                });
                
                try {
                    // Get configuration file path based on user-provided memory offset information
                    // Offset from this to this.main is 40
                    const mainPtr = this.settingsThis.add(40).readPointer();
                    if (mainPtr.isNull()) {
                        sendEvent("error", {
                            error_type: "get_main_failed",
                            message: "Failed to get main pointer"
                        });
                        return;
                    }
                    
                    // Offset from this.main to this.main.basicConfig is 800
                    const basicConfigPtr = mainPtr.add(800).readPointer();
                    if (basicConfigPtr.isNull()) {
                        sendEvent("error", {
                            error_type: "get_basic_config_failed",
                            message: "Failed to get basicConfig pointer"
                        });
                        return;
                    }
                    
                    // this.main.basicConfig.config.file pointer and this.main.basicConfig pointer have the same address
                    // this.main.basicConfig.config.file pointer points to a string pointer
                    const configFilePtr = basicConfigPtr.readPointer();
                    if (configFilePtr.isNull()) {
                        sendEvent("error", {
                            error_type: "get_config_file_failed",
                            message: "Failed to get config.file pointer"
                        });
                        return;
                    }
                    
                    const configFilePath = configFilePtr.readCString();
                    if (!configFilePath) {
                        sendEvent("error", {
                            error_type: "read_config_file_path_failed",
                            message: "Failed to read configuration file path"
                        });
                        return;
                    }
                    
                    // Send configuration file path to handler.py for reading and checking file content
                    // This ensures the file is fully written before checking its content
                    sendEvent("config_file_found", {
                        message: "Found configuration file path",
                        path: configFilePath
                    });
                    
                } catch (e) {
                    sendEvent("error", {
                        error_type: "check_config_failed",
                        message: `Failed to check configuration file: ${e.message}`
                    });
                }
            }
        });
    }

    // Initialize start recording hook
    function initStartRecordingHook() {
        const startRecordingFuncAddr = getFunctionAddress(FUNCTION_StartRecording);
        if (!startRecordingFuncAddr) {
            return;
        }

        Interceptor.attach(startRecordingFuncAddr, {
            onEnter: function(args) {
                sendEvent("start_recording_called", {
                    message: "Intercepted start recording function call"
                });
            },

            onLeave: function(retval) {
                sendEvent("start_recording_returned", {
                    message: "Start recording function returned",
                    result: retval.toInt32() !== 0
                });
            }
        });
    }

    // Initialize stop recording hook
    function initStopRecordingHook() {
        const stopRecordingFuncAddr = getFunctionAddress(FUNCTION_StopRecording);
        if (!stopRecordingFuncAddr) {
            return;
        }

        Interceptor.attach(stopRecordingFuncAddr, {
            onEnter: function(args) {
                this.force = args[1].toInt32() !== 0;
                sendEvent("stop_recording_called", {
                    message: "Intercepted stop recording function call",
                    force: this.force
                });
            },

            onLeave: function(retval) {
                sendEvent("stop_recording_returned", {
                    message: "Stop recording function returned"
                });
            }
        });
    }

    // Initialize hooks
    function initHook() {
        sendEvent("script_initialized", {
            message: "OBS recording output configuration and test monitoring script has started"
        });

        // Initialize hooks
        initConfigureRecordingHook();
        initSaveOutputSettingsHook();
        initStartRecordingHook();
        initStopRecordingHook();
        
        sendEvent("hook_installed", {
            message: "Recording output configuration and test monitoring hooks installed, waiting for operation..."
        });
    }

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


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

import time
from typing import Dict, Any, Optional, List

key_steps = []

start_recording = False


def message_handler(
    message: Dict[str, Any], logger, task_parameter: Dict[str, Any]
) -> Optional[List[Dict[str, Any]]]:
    global key_steps, start_recording
    payload = message["payload"]
    print(payload)
    event_type = payload["event"]
    logger.debug(f"Received event: {event_type}")
    if event_type == "config_file_found":
        config_file_path = payload.get("path", "")
        logger.info(f"Found configuration file path: {config_file_path}")

        # Wait for a while to ensure the file has been written
        time.sleep(1)

        # Read the configuration file content
        try:
            with open(config_file_path, "r") as f:
                config_content = f.read()

            # Parse the configuration file content to find the value of RecFormat2 in the [SimpleOutput] section
            lines = config_content.split("\n")
            in_simple_output_section = False
            rec_format2_value = None
            file_path_value = None

            for line in lines:
                trimmed_line = line.strip()

                if trimmed_line == "[SimpleOutput]":
                    in_simple_output_section = True
                    continue

                if in_simple_output_section and trimmed_line.startswith("["):
                    # Already left the [SimpleOutput] section
                    break

                if in_simple_output_section and trimmed_line.startswith("RecFormat2="):
                    rec_format2_value = trimmed_line[len("RecFormat2=") :]

                if in_simple_output_section and trimmed_line.startswith("FilePath="):
                    file_path_value = trimmed_line[len("FilePath=") :]

            # Check the FilePath value
            if file_path_value is not None:
                logger.info(
                    f"Found FilePath value in the configuration file: {file_path_value}"
                )
                expected_path = task_parameter.get("output_path", "")

                if expected_path in file_path_value:
                    key_steps.append({"status": "key_step", "index": 1})
                    logger.info(
                        f"Recording output path has been correctly configured as: {file_path_value}"
                    )
                else:
                    logger.warning(
                        f"Recording output path configuration does not match, expected: {expected_path}, actual: {file_path_value}"
                    )
            else:
                logger.warning("FilePath setting not found in the configuration file")

            # Check the RecFormat2 value
            if rec_format2_value is not None:
                logger.info(
                    f"Found RecFormat2 value in the configuration file: {rec_format2_value}"
                )
                expected_format = task_parameter.get("output_format", "")

                if rec_format2_value == expected_format:
                    key_steps.append({"status": "key_step", "index": 2})
                    logger.info(
                        f"Recording output format has been correctly configured as: {rec_format2_value}"
                    )
                else:
                    logger.warning(
                        f"Recording output format configuration does not match, expected: {expected_format}, actual: {rec_format2_value}"
                    )
            else:
                logger.warning("RecFormat2 setting not found in the configuration file")

        except Exception as e:
            logger.error(f"Failed to read or parse the configuration file: {str(e)}")

        return key_steps

    elif event_type == "output_path_configured":
        # Retain this event handler in case the path is obtained directly from the API
        logger.info("Recording output path obtained through API")
        configured_path = payload.get("path", "")
        expected_path = task_parameter.get("output_path", "")

        if expected_path in configured_path:
            logger.info(
                f"Recording output path has been correctly configured as: {configured_path}"
            )
            if not dict_have_index(key_steps, 1):
                key_steps.append({"status": "key_step", "index": 1})
        else:
            logger.warning(
                f"Recording output path configuration does not match, expected: {expected_path}, actual: {configured_path}"
            )

        return key_steps

    elif event_type == "output_format_configured":
        # Retain this event handler in case the format is obtained directly from the API
        logger.info("Recording output format obtained through API")
        configured_format = payload.get("format", "")
        expected_format = task_parameter.get("output_format", "")

        if configured_format == expected_format:
            if not dict_have_index(key_steps, 2):
                key_steps.append({"status": "key_step", "index": 2})
            logger.info(
                f"Recording output format has been correctly configured as: {configured_format}"
            )
        else:
            logger.warning(
                f"Recording output format configuration does not match, expected: {expected_format}, actual: {configured_format}"
            )

        return key_steps

    elif event_type == "start_recording_called":
        start_recording = True
        logger.info("Recording started")

    elif event_type == "stop_recording_called":
        # If both start and stop recording are called, consider the recording functionality tested
        if start_recording:
            if not dict_have_index(key_steps, 3):
                key_steps.append({"status": "key_step", "index": 3})
            logger.info("Recording functionality successfully tested")
            print(key_steps)
            # Check if all success conditions are met
            if len(key_steps) == 3:
                key_steps.append(
                    {
                        "status": "success",
                        "reason": "Output path and format both match, and recording test completed",
                    }
                )
                return key_steps

    return None


def dict_have_index(key_steps: List[Dict[str, Any]], index: int) -> bool:
    # Check if the key step with the given index exists in the list
    for key_step in key_steps:
        if "index" in key_step and key_step["index"] == index:
            return True

    return False