Task configuration:
{"task_id": "21", "task_name": "batch_manage_color_sources", "description": "Batch add, reorder and delete color sources", "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", "add_color_sources": ["c1", "c2", "c3", "c4"], "reorder_to": ["c3", "c1", "c4", "c2"], "delete_sources": ["c2", "c4"]}, "instruction_template": "Please batch add color sources ${add_color_sources} in ${scene_name} in obs-studio, then reorder them from bottom to top as ${reorder_to}, and finally delete ${delete_sources}", "evaluation_setup": {"scripts": [{"role": "hook", "path": "hooker.js"}, {"role": "handler", "path": "handler.py"}], "timeout": 600}, "preconditions": {"obs_studio_running": true}, "total_key_steps": 3, "events": {"add_success": {"description": "Color sources added successfully", "is_key_step": true, "key_step_index": 1, "key_step_name": "Color sources added successfully"}, "reorder_success": {"description": "Color sources reordered successfully", "is_key_step": true, "key_step_index": 2, "key_step_name": "Color sources reordered successfully"}, "delete_success": {"description": "Color sources deleted successfully", "is_key_step": true, "key_step_index": 3, "key_step_name": "Color sources deleted successfully"}}, "mcp_servers": [{"command": "node", "args": ["/home/agent/obs-mcp/build/index.js"]}]}

Hook script:
(function () {
    // Related symbols
    const FUNC_CREATE = "obs_source_create";
    const FUNC_REMOVE = "obs_source_remove";
    const FUNC_SET_ORDER = "obs_sceneitem_set_order";
    const FUNC_SAVE = "OBSBasic::Save";
    const FUNC_SAVE_SYMBOL = "_ZN8OBSBasic4SaveEPKc";

    const EVENT_ON_ENTER = "function called";
    const EVENT_ON_LEAVE = "function returned";
    const EVENT_ON_SUCCESS = "scene_json_path";
    const PAYLOAD_SUCCESS = "path";

    const MESSAGE_SCRIPT_INITIALIZED = "Monitoring script has started";
    const MESSAGE_HOOK_INSTALLED = "Monitoring hook installed, waiting for operation...";
    const MESSAGE_ON_SUCCESS = "Batch management of color sources operation completed";

    // Record operations
    let addCount = 0, removeCount = 0, reorderCount = 0;
    let lastScenePath = null;

    function sendEvent(eventType, data = {}) {
        const payload = {
            event: eventType,
            ...data,
            timestamp: new Date().getTime()
        };
        send(payload);
    }

    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;
    }

    // Hook: Add color source
    function hookCreate() {
        const addr = getFunctionAddress(FUNC_CREATE);
        if (!addr) return;
        Interceptor.attach(addr, {
            onEnter(args) {
                const name = args[1].readUtf8String();
                const type = args[0].readUtf8String();
                if (type === "color_source") {
                    addCount++;
                    sendEvent(EVENT_ON_ENTER, { function: FUNC_CREATE, name });
                }
            },
            onLeave(retval) {
                sendEvent(EVENT_ON_LEAVE, { function: FUNC_CREATE });
            }
        });
    }

    // Hook: Remove color source
    function hookRemove() {
        const addr = getFunctionAddress(FUNC_REMOVE);
        if (!addr) return;
        Interceptor.attach(addr, {
            onEnter(args) {
                removeCount++;
                sendEvent(EVENT_ON_ENTER, { function: FUNC_REMOVE });
            },
            onLeave(retval) {
                sendEvent(EVENT_ON_LEAVE, { function: FUNC_REMOVE });
            }
        });
    }

    // Hook: Reorder scene items
    function hookSetOrder() {
        const addr = getFunctionAddress(FUNC_SET_ORDER);
        if (!addr) return;
        Interceptor.attach(addr, {
            onEnter(args) {
                reorderCount++;
                sendEvent(EVENT_ON_ENTER, { function: FUNC_SET_ORDER });
            },
            onLeave(retval) {
                sendEvent(EVENT_ON_LEAVE, { function: FUNC_SET_ORDER });
            }
        });
    }

    // Hook: Save scene, get JSON path
    function hookSave() {
        const addr = getFunctionAddress(FUNC_SAVE_SYMBOL);
        if (!addr) return;
        Interceptor.attach(addr, {
            onEnter(args) {
                this.file = args[1].readCString(-1);
                sendEvent(EVENT_ON_ENTER, { function: FUNC_SAVE });
            },
            onLeave(retval) {
                lastScenePath = this.file;
                sendEvent(EVENT_ON_LEAVE, { function: FUNC_SAVE });
                if (lastScenePath && lastScenePath.endsWith(".json")) {
                    sendEvent(EVENT_ON_SUCCESS, {
                        message: MESSAGE_ON_SUCCESS,
                        [PAYLOAD_SUCCESS]: lastScenePath
                    });
                }
            }
        });
    }

    function initHook() {
        sendEvent("script_initialized", { message: MESSAGE_SCRIPT_INITIALIZED });
        hookCreate();
        hookRemove();
        hookSetOrder();
        hookSave();
        sendEvent("hook_installed", { message: MESSAGE_HOOK_INSTALLED });
    }

    initHook();
})();

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

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

_EVENT_SUCCESS = "scene_json_path"
_PAYLOAD_SUCCESS = "path"

key_steps = []


def message_handler(
    message: Dict[str, Any], logger, task_parameter: Dict[str, Any]
) -> Optional[List[Dict[str, Any]]]:
    global key_steps
    print(message)
    payload = message["payload"]
    print(payload)
    event_type = payload["event"]
    logger.debug(f"Received event: {event_type}")
    if event_type == _EVENT_SUCCESS:
        logger.info(payload.get("message", ""))
        scene_name = task_parameter.get("scene_name", "")
        add_sources = task_parameter.get("add_color_sources", [])
        reorder_to = task_parameter.get("reorder_to", [])
        delete_sources = task_parameter.get("delete_sources", [])
        current_file_path = payload.get(_PAYLOAD_SUCCESS, "")
        with open(current_file_path, "r") as f:
            data = json.load(f)

            # Check the order and deletion status of color sources in the scene
            scene_items = data.get("sources", [])
            for scene_item in scene_items:
                if scene_item.get("name") == scene_name:
                    settings_items = scene_item.get("settings", {}).get("items", [])
                    matched_items = [
                        item.get("name")
                        for item in settings_items
                        if item.get("name") in add_sources
                    ]
                    if matched_items == add_sources:
                        if not dict_have_index(key_steps, 1):
                            key_steps.append({"status": "key_step", "index": 1})
                            return [{"status": "key_step", "index": 1}]
                    if matched_items == reorder_to:
                        if not dict_have_index(key_steps, 2):
                            key_steps.append({"status": "key_step", "index": 2})
                            return [{"status": "key_step", "index": 2}]
                    expected_final = [
                        name for name in reorder_to if name not in delete_sources
                    ]
                    print(matched_items)
                    print(expected_final)
                    if matched_items == expected_final:
                        return [
                            {"status": "key_step", "index": 3},
                            {
                                "status": "success",
                                "reason": "Color sources added, reordered, and deleted successfully",
                            },
                        ]
    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