Task configuration:
{"task_id": "04", "task_name": "update_font_size", "description": "\u5728vscode\u5de5\u4f5c\u533a\u4e2d\u4fee\u6539\u6216\u521b\u5efa.vscode/settings.json\u6587\u4ef6, \u5c06\u5b57\u4f53\u7684\u5927\u5c0f\u8bbe\u7f6e\u4e3a16", "application_info": {"name": "vscode", "executable_path": "/usr/share/code/code", "args": ["--no-sandbox", "--user-data-dir=/root/vscode_user_data_dir/"]}, "task_parameters": {"font_size": 16, "expected_file_path": "/root/C-Plus-Plus/.vscode/settings.json"}, "instruction_template": "\u5728vscode\u5f53\u524d\u5de5\u4f5c\u533a\u4e2d\u4fee\u6539\u6216\u521b\u5efa.vscode/settings.json\u914d\u7f6e\u6587\u4ef6, \u5c06\u5b57\u4f53\u5927\u5c0f\u8bbe\u7f6e\u4e3a ${font_size}, \u6ce8\u610f\u4fdd\u5b58\u6587\u4ef6", "evaluation_setup": {"scripts": [{"role": "hook", "path": "hooker.js"}, {"role": "handler", "path": "handler.py"}], "timeout": 120, "evaluator_type": "IpcInjector", "evaluate_on_completion": true}, "total_key_steps": 2, "preconditions": {"vscode_running": true}, "events": {"read_origin_content": {"description": "\u4efb\u52a1\u5f00\u59cb\u65f6\u8bfb\u53d6settings.json\u6587\u4ef6\u5185\u5bb9", "is_key_step": false}, "evaluate_on_completion": {"description": "\u5728\u4efb\u52a1\u7ed3\u675f\u65f6\u5224\u65adsettings.json\u914d\u7f6e\u6587\u4ef6\u662f\u5426\u7b26\u5408\u9884\u671f", "is_key_step": true, "key_step_index": 2, "key_step_name": "\u5728\u4efb\u52a1\u7ed3\u675f\u65f6\u68c0\u67e5\u914d\u7f6e\u6587\u4ef6\u662f\u5426\u6b63\u786e"}, "open_file": {"description": "\u6253\u5f00\u6587\u4ef6", "is_key_step": true, "key_step_index": 1, "key_step_name": "\u5728vscode\u4e2d\u6253\u5f00\u6587\u4ef6"}}, "mcp_servers": [{"command": "mcp-proxy", "args": ["http://127.0.0.1:6010/sse"]}], "context_data": [{"from": "tests/context_data/vscode/user_data", "to": "/root/vscode_user_data_dir"}, {"from": "tests/context_data/vscode/extensions", "to": "/root/.vscode/"}, {"from": "tests/context_data/vscode/C-Plus-Plus", "to": "/root/C-Plus-Plus"}]}

Hook script:
/**
 * Reads the .vscode/settings.json file from the current workspace and returns its contents as a JSON object.
 * Returns an empty object if the file does not exist or is invalid.
 * @returns {Promise<object>} The JSON contents of settings.json or an empty object
 */
async function readWorkspaceSettings() {
    try {
        // Get the workspace folders
        const workspaceFolders = vscode.workspace.workspaceFolders;
        if (!workspaceFolders || workspaceFolders.length === 0) {
            return {};
        }
        
        // Use the first workspace folder (multi-root workspaces would need additional handling)
        const workspacePath = workspaceFolders[0].uri;
        // vscode.window.showInformationMessage(workspacePath);
        const settingsFileUri = vscode.Uri.joinPath(workspacePath, '.vscode', 'settings.json');

        // Read the file
        const fileContent = await vscode.workspace.fs.readFile(settingsFileUri);
        const fileContentString = Buffer.from(fileContent).toString('utf8');

        // Parse JSON
        return JSON.parse(fileContentString);
    } catch (error) {
        // Return empty object if file doesn't exist or is invalid
        if (error instanceof vscode.FileSystemError && error.code === 'FileNotFound') {
            return {};
        }
        // Handle JSON parse errors or other issues
        return {};
    }
}

// vscode.window.showInformationMessage(`代码成功注入`);
readWorkspaceSettings().then(settings => {
    socket.emit("send", {
        'event_type': "read_origin_content",
        'message': "任务开始时读取settings.json文件内容",
        'data': settings
    });
});

// Listen for 'evaluate' event
socket.on('evaluate', async () => {
    const settings = await readWorkspaceSettings();
    // vscode.window.showInformationMessage(message);
    socket.emit("send", {
        'event_type': "evaluate_on_completion",
        'message': "任务结束时读取文件内容",
        'data': settings
    });
});

// Register a listener for when a text document is opened
vscode.workspace.onDidOpenTextDocument(async (document) => {
    const filePath = document.uri.fsPath;
    socket.emit("send", {
        event_type: "open_file",
        message: "打开文件",
        path: filePath,
        scheme: document.uri.scheme
    });
});

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

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

origin_file_content = None

def message_handler(message: Dict[str, Any], logger, task_parameter: Dict[str, Any]) -> Optional[List[Dict[str, Any]]]:    
    global origin_file_content
    event_type = message.get('event_type')
    expected_file_name = task_parameter.get("expected_file_path", "/root/C-Plus-Plus/.vscode/settings.json")
    logger.info(message)
    if event_type == "read_origin_content":
        origin_file_content = message.get('data')
    elif event_type == "evaluate_on_completion":
        file_content = message.get('data')
        expected_font_size = task_parameter.get("font_size", 16)
        if "editor.fontSize" in file_content:
            font_size = file_content.pop("editor.fontSize")
            if "editor.fontSize" in origin_file_content:
                origin_file_content.pop("editor.fontSize")
            if font_size == expected_font_size and file_content == origin_file_content:
                return [
                    {"status": "key_step", "index": 2},
                    {"status": "success", "reason": f"成功配置文件"}
                ]
        return [{"status": "error", "type": "evaluate_on_completion", "message": "任务没有完成"}]
    elif event_type == "open_file":
        file_path = message.get("path")
        if message.get("scheme") == "git":
            file_path = file_path[:-4]
        if file_path == expected_file_name:
            return [{"status": "key_step", "index": 1}]
    return None