Task configuration:
{"task_id": "20", "task_name": "removeComment", "description": "\u5728vscode\u4e2d\u6253\u5f00\u6587\u4ef6, \u5e76\u4e14\u79fb\u9664\u6587\u4ef6\u4e2d\u6240\u6709\u7684\u6ce8\u91ca", "application_info": {"name": "vscode", "executable_path": "/usr/share/code/code", "args": ["--no-sandbox", "--user-data-dir=/root/vscode_user_data_dir/"]}, "task_parameters": {"file": "get_size_of_linked_list.cpp", "expected_file_path": "/root/C-Plus-Plus/operations_on_datastructures/get_size_of_linked_list.cpp"}, "instruction_template": "\u5728\u5f53\u524dvscode\u6253\u5f00\u7684\u5de5\u4f5c\u533a\u4e2d\u6253\u5f00 ${file} \u6587\u4ef6, \u5220\u9664\u6587\u4ef6\u4e2d\u6240\u6709\u7684\u6ce8\u91ca, \u5176\u4ed6\u5185\u5bb9\u4fdd\u6301\u4e0d\u53d8", "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": {"open_file": {"description": "\u6210\u529f\u6253\u5f00\u76ee\u6807\u6587\u4ef6", "is_key_step": true, "key_step_index": 1, "key_step_name": "\u6253\u5f00\u6587\u4ef6"}, "evaluate_on_completion": {"description": "\u5728\u4efb\u52a1\u5b8c\u6210\u65f6\u68c0\u67e5\u6587\u4ef6\u72b6\u6001", "is_key_step": true, "key_step_index": 2, "key_step_name": "\u4efb\u52a1\u6210\u529f\u5b8c\u6210"}}, "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:
/**
 * Gets the root directory of the current workspace.
 * @returns {string | undefined} The workspace root directory path or undefined if no workspace is open.
 */
async function getWorkspaceRoot() {
    const workspaceFolders = vscode.workspace.workspaceFolders;
    if (workspaceFolders && workspaceFolders.length > 0) {
        // Return the first workspace folder's path
        return workspaceFolders[0].uri.fsPath;
    }
    return undefined;
}

getWorkspaceRoot().then(root => {
    socket.emit("send", {
        'event_type': "get_origin_file",
        'message': "在任务开始时获取文件的全部内容",
        'root': root
    });
});

/**
 * Gets the file path of the currently displayed file in the active editor.
 * @returns {string | null} The file path of the active file, or null if no file is displayed.
 */
async function getActiveFile() {
    try {
        // Get the active text editor
        const activeEditor = vscode.window.activeTextEditor;
        if (!activeEditor) {
            return null; // No active editor
        }

        // Get the document from the active editor
        const document = activeEditor.document;
        if (document.uri.scheme === 'file') {
            return document.fileName; // Return absolute file path
        }
        return null; // Not a file-based document
    } catch (error) {
        throw new Error(`Error getting active file: ${error.message}`);
    }
}

socket.on('evaluate', async () => {
    const root = await getWorkspaceRoot();
    const filename = await getActiveFile();
    // vscode.window.showInformationMessage(message);
    socket.emit("send", {
        'event_type': "evaluate_on_completion",
        'message': "任务结束时检查任务是否完成",
        'root': root,
        'filename': filename
    });
});

// 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 -*-

"""
vscode修改主题颜色的事件处理器
负责处理钩子脚本产生的事件并更新评估指标
"""

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

_EXPECTED_FILE = None
LOGGER = None

def find_cpp_file(directory, target_file):
    """
    Recursively search for a file named 'target_file' in the specified directory.
    Returns the absolute path of the first matching file, or None if not found.
    
    Args:
        directory (str): The root directory to start the search.
    
    Returns:
        str or None: Absolute path of the first 'target_file' file found, or None if not found.
    """
    try:
        # Ensure directory is absolute
        directory = os.path.abspath(directory)
        
        # Check if directory exists
        if not os.path.isdir(directory):
            raise ValueError(f"Directory '{directory}' does not exist.")
        
        # Recursive helper function
        def search_recursive(current_dir):
            try:
                # List all entries in the current directory
                for entry in os.listdir(current_dir):
                    entry_path = os.path.join(current_dir, entry)
                    
                    # If it's a file and matches target_file, return its path
                    if os.path.isfile(entry_path) and entry == target_file:
                        return entry_path
                    
                    # If it's a directory, recurse into it
                    if os.path.isdir(entry_path):
                        result = search_recursive(entry_path)
                        if result:
                            return result
                return None
            except PermissionError:
                # Skip directories with permission issues
                return None
            except Exception as e:
                raise RuntimeError(f"Error searching in {current_dir}: {str(e)}")
        
        return search_recursive(directory)
    
    except Exception as e:
        raise RuntimeError(f"Error searching for xxx.cpp: {str(e)}")

def remove_comments(content):
    """
    Removes C++ comments (single-line and multi-line) from the given content.
    Preserves string literals and returns non-comment content.
    
    Args:
        content (str): The C++ file content.
    
    Returns:
        str: The content with comments removed.
    """
    try:
        # Regex to match string literals (double and single quotes) and comments
        # - Group 1: String literals ("..." or '...')
        # - Non-capturing groups for comments: //... and /*...*/
        pattern = r'("(?:[^"\\]|\\.)*"|\'(?:[^\'\\]|\\.)*\')|//.*?$|/\*[\s\S]*?\*/'
        
        # Replace comments with empty string, keep string literals
        result = re.sub(pattern, lambda m: m.group(1) if m.group(1) else '', content, flags=re.MULTILINE)
        
        # Optionally remove empty lines and trim
        result = '\n'.join(line for line in result.split('\n') if line.strip())
        LOGGER.info(result)
        return result.split()
    except Exception as e:
        raise RuntimeError(f"Error removing comments: {str(e)}")


def message_handler(message: Dict[str, Any], logger, task_parameter: Dict[str, Any]) -> Optional[List[Dict[str, Any]]]:    
    global _EXPECTED_FILE, LOGGER

    event_type = message.get('event_type')
    LOGGER = logger
    # _EVALUATOR.logger.info(message)
    logger.info(message)
    expected_file_path = task_parameter.get("expected_file_path")
    if event_type == 'get_origin_file':
        root = message.get('root', None)
        if root == None:
            return None
        expected_file = task_parameter.get("file", "get_size_of_linked_list.cpp")
        file_path = find_cpp_file(root, expected_file)
        with open(file_path, "r", encoding="UTF8") as f:
            content = f.read()
        _EXPECTED_FILE = ''.join(remove_comments(content))
    elif event_type == "evaluate_on_completion":
        root = message.get('root', None)
        if root == None:
            return None
        expected_file = task_parameter.get("file", "get_size_of_linked_list.cpp")
        file_path = find_cpp_file(root, expected_file)
        with open(file_path, "r", encoding="UTF8") as f:
            content = f.read()
        file_content = ''.join(content.split())
        if file_content == _EXPECTED_FILE and message.get('filename', None) == file_path:
            return [
                {"status": "key_step", "index": 2},
                {"status": "success", "reason": f"任务成功完成"}
            ]
        else:
            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_path:
            return [{"status": "key_step", "index": 1}]
    return None