Task configuration:
{"task_id": "12", "task_name": "debug_until", "description": "\u5728vscode\u4e2d\u6253\u5f00\u7279\u5b9a\u76ee\u5f55, \u914d\u7f6e\u8c03\u8bd5c++\u7684\u914d\u7f6e\u6587\u4ef6, \u6253\u5f00\u6587\u4ef6, \u8bbe\u7f6e\u65ad\u70b9, \u5f00\u59cb\u8c03\u8bd5\u76f4\u5230\u89e6\u53d1\u7279\u5b9a\u6761\u4ef6", "application_info": {"name": "vscode", "executable_path": "/usr/share/code/code", "args": ["--no-sandbox", "--user-data-dir=/root/vscode_user_data_dir/"]}, "task_parameters": {"directory": "/root/C-Plus-Plus/agent_test", "file_name": "debug_until.cpp", "breakpoints": {"17": "i==3", "19": "j==2", "22": ""}, "state": "next=5", "expected_hit_file": "/root/C-Plus-Plus/agent_test/debug_until.cpp", "expected_hit_line": 22, "expected_locals": {"next": "5", "place": "1", "i": "5", "n": "10", "k": "2", "seq.[0]": "3", "seq.[1]": "2"}, "expected_program": "/root/C-Plus-Plus/agent_test/debug_until"}, "instruction_template": "\u5728vscode\u4e2d\u5c06\u5de5\u4f5c\u533a\u5207\u6362\u5230 ${directory} \u76ee\u5f55, \u914d\u7f6e\u8c03\u8bd5 ${file_name} \u7684\u914d\u7f6e\u6587\u4ef6, \u7136\u540e\u6253\u5f00\u8be5\u6587\u4ef6, \u8bbe\u7f6e\u65ad\u70b9, \u65ad\u70b9\u7684\u884c\u53f7\u548c\u6761\u4ef6\u5982\u4e0b: ${breakpoints}, \u6700\u540e\u5f00\u59cb\u8c03\u8bd5, \u4e00\u76f4continue\u5230 ${state}", "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": 6, "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"}, "breakpoint_change_17": {"description": "\u6dfb\u52a0\u6216\u8005\u4fee\u653917\u884c\u65ad\u70b9\u4fe1\u606f", "is_key_step": true, "key_step_index": 2, "key_step_name": "\u6210\u529f\u6dfb\u52a0\u6216\u8005\u4fee\u653917\u884c\u65ad\u70b9\u4fe1\u606f"}, "breakpoint_change_19": {"description": "\u6dfb\u52a0\u6216\u8005\u4fee\u653919\u884c\u65ad\u70b9\u4fe1\u606f", "is_key_step": true, "key_step_index": 3, "key_step_name": "\u6210\u529f\u6dfb\u52a0\u6216\u8005\u4fee\u653919\u884c\u65ad\u70b9\u4fe1\u606f"}, "breakpoint_change_22": {"description": "\u6dfb\u52a0\u6216\u8005\u4fee\u653922\u884c\u65ad\u70b9\u4fe1\u606f", "is_key_step": true, "key_step_index": 4, "key_step_name": "\u6210\u529f\u6dfb\u52a0\u6216\u8005\u4fee\u653922\u884c\u65ad\u70b9\u4fe1\u606f"}, "debug_session_start": {"description": "\u5f00\u542fdebug\u4f1a\u8bdd", "is_key_step": true, "key_step_index": 5, "key_step_name": "\u6210\u529f\u5f00\u542fdebug\u4f1a\u8bdd"}, "evaluate_on_completion": {"description": "\u5728\u4efb\u52a1\u7ed3\u675f\u65f6\u68c0\u67e5\u63d2\u4ef6\u662f\u5426\u5df2\u7ecf\u5b89\u88c5", "is_key_step": true, "key_step_index": 6, "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 all breakpoints in the current workspace
 * @returns {Promise<Array<{file: string, line: number, condition: string}>>}
 */
async function getAllBreakpoints() {
    const breakpoints = vscode.debug.breakpoints;
    const breakpointDetails = [];

    for (const bp of breakpoints) {
        if (bp instanceof vscode.SourceBreakpoint) {
            const location = bp.location;
            breakpointDetails.push({
                file: location.uri.fsPath,
                line: location.range.start.line + 1, // VS Code lines are 0-based
                condition: bp.condition || ''
            });
        }
    }

    return breakpointDetails;
}

/**
 * Recursively fetches variables and their values
 * @param {vscode.DebugSession} session - The active debug session
 * @param {number} variablesReference - Reference to the variables
 * @param {string} parentName - Name of the parent variable (for nested display)
 * @returns {Promise<Array<{name: string, value: string}>>}
 */
async function fetchVariables(session, variablesReference, parentName = '') {
    if (variablesReference <= 0) {
        return [];
    }

    try {
        const variablesResponse = await session.customRequest('variables', { variablesReference });
        const variables = variablesResponse?.variables || [];
        const result = [];

        for (const variable of variables) {
            const varName = parentName ? `${parentName}.${variable.name}` : variable.name;
            let varValue = variable.value;

            // If the variable has a reference (e.g., object, array, std::vector), fetch its children
            if (variable.variablesReference > 0 && variable.value === '{...}') {
                const childVariables = await fetchVariables(session, variable.variablesReference, varName);
                if (childVariables.length > 0) {
                    result.push(...childVariables);
                } else {
                    // Fallback to the original value if no children are found
                    result.push({ name: varName, value: varValue });
                }
            } else {
                result.push({ name: varName, value: varValue });
            }
        }

        return result;
    } catch (error) {
        console.error(`Error fetching variables for reference ${variablesReference}:`, error);
        return [];
    }
}

/**
 * Gets information about the current debug session
 * @returns {Promise<{file: string, line: number, locals: Array<{name: string, value: string}>} | null>}
 */
async function getCurrentDebugSessionInfo() {
    const activeSession = vscode.debug.activeDebugSession;
    if (!activeSession) {
        console.log('No active debug session found');
        return null;
    }

    try {
        // Get threads
        const threadsResponse = await activeSession.customRequest('threads');
        const threadId = threadsResponse?.threads[0]?.id;
        if (!threadId) {
            console.log('No threads found in debug session');
            return null;
        }

        // Get stack frames
        const stackFramesResponse = await activeSession.customRequest('stackTrace', { threadId });
        const stackFrames = stackFramesResponse?.stackFrames;
        if (!stackFrames || stackFrames.length === 0) {
            console.log('No stack frames available');
            return null;
        }

        const topFrame = stackFrames[0];
        const sourcePath = topFrame.source?.path || 'Unknown';

        // Get scopes for the top frame
        const scopesResponse = await activeSession.customRequest('scopes', { frameId: topFrame.id });
        const scopes = scopesResponse?.scopes;
        if (!scopes || scopes.length === 0) {
            console.log('No scopes found for the current frame');
            return null;
        }

        // Collect local variables
        const locals = [];
        for (const scope of scopes) {
            if (scope.name === 'Locals' && scope.variablesReference > 0) {
                const scopeVariables = await fetchVariables(activeSession, scope.variablesReference);
                locals.push(...scopeVariables);
            }
        }

        return {
            file: sourcePath,
            line: topFrame.line,
            locals
        };
    } catch (error) {
        console.error('Error retrieving debug session info:', error);
        return null;
    }
}

// Listen for 'evaluate' event
socket.on('evaluate', async () => {
    const breakpoint_info = await getAllBreakpoints();
    const debug_session_info = await getCurrentDebugSessionInfo();
    // vscode.window.showInformationMessage(message);
    socket.emit("send", {
        'event_type': "evaluate_on_completion",
        'message': "任务结束时获取到debug会话的状态",
        'breakpoints': breakpoint_info,
        'debuginfo': debug_session_info
    });
});

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

// Register a listener for when breakpoints change
vscode.debug.onDidChangeBreakpoints((event) => {
    try {
        // Process added breakpoints
        event.added.forEach((breakpoint) => {
            if (breakpoint instanceof vscode.SourceBreakpoint) {
                const location = breakpoint.location;
                socket.emit('send', {
                    'event_type': 'breakpoint_change',
                    'message': '断点添加',
                    'action': 'added',
                    'path': location.uri.fsPath,
                    'line': location.range.start.line + 1, // Convert to 1-based line number
                    'condition': breakpoint.condition || '',
                    'enabled': breakpoint.enabled
                });
            }
        });

        // Process removed breakpoints
        event.removed.forEach((breakpoint) => {
            if (breakpoint instanceof vscode.SourceBreakpoint) {
                const location = breakpoint.location;
                socket.emit('send', {
                    'event_type': 'breakpoint_change',
                    'message': '断点移除',
                    'action': 'removed',
                    'path': location.uri.fsPath,
                    'line': location.range.start.line + 1,
                    'condition': breakpoint.condition || '',
                    'enabled': breakpoint.enabled
                });
            }
        });

        // Process changed breakpoints
        event.changed.forEach((breakpoint) => {
            if (breakpoint instanceof vscode.SourceBreakpoint) {
                const location = breakpoint.location;
                socket.emit('send', {
                    'event_type': 'breakpoint_change',
                    'message': '断点修改',
                    'action': 'changed',
                    'path': location.uri.fsPath,
                    'line': location.range.start.line + 1,
                    'condition': breakpoint.condition || '',
                    'enabled': breakpoint.enabled
                });
            }
        });

        console.log(`Breakpoints changed: added=${event.added.length}, removed=${event.removed.length}, changed=${event.changed.length}`);
    } catch (error) {
        console.error(`Error handling breakpoint change: ${error.message}`);
    }
});

vscode.debug.onDidStartDebugSession((session) => {
    try {
        // Get the program path and working directory from the configuration
        const program = session.configuration.program || 'unknown';
        const cwd = session.configuration.cwd || session.workspaceFolder?.uri.fsPath || 'unknown';

        // Emit debug session start event via socket
        socket.emit('send', {
            'event_type': 'debug_session_start',
            'message': '调试会话开始',
            'session_name': session.name,
            'debugger_type': session.type,
            'program': program,
            'working_dir': cwd
        });

        console.log(`Debug session started: name=${session.name}, type=${session.type}, program=${program}`);
    } catch (error) {
        console.error(`Error handling debug session start: ${error.message}`);
    }
});

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

from typing import Dict, Any, Optional, List

def message_handler(message: Dict[str, Any], logger, task_parameter: Dict[str, Any]) -> Optional[List[Dict[str, Any]]]:    
    event_type = message.get('event_type')
    logger.info(message)
    expected_hit_file = task_parameter.get("expected_hit_file", '/root/C-Plus-Plus/agent_test/debug_until.cpp')
    expected_hit_line = task_parameter.get("expected_hit_line", 22)
    expected_locals = task_parameter.get("expected_locals", {
        "next": "5",
        "place": "1",
        "i": "5",
        "n": "10",
        "k": "2",
        "seq.[0]": "3",
        "seq.[1]": "2",
    })
    expected_breakpoints = task_parameter.get("breakpoints", {
        "17": "i==3",
        "19": "j==2",
        "22": ""
    })
    expected_program = task_parameter.get("expected_program", "/root/C-Plus-Plus/agent_test/debug_until")
    if event_type == "evaluate_on_completion":
        breakpoints = message.get('breakpoints')
        debuginfo = message.get('debuginfo')
        if not debuginfo or not breakpoints:
            return None
        for bp in breakpoints:
            file = bp.get('file', None)
            line = str(bp.get('line', None))
            condition = bp.get('condition', None)
            if file == expected_hit_file and expected_breakpoints.get(line, None) == condition:
                expected_breakpoints.pop(line)
        if debuginfo.get('file', None) != expected_hit_file or debuginfo.get('line', None) != expected_hit_line:
            return None
        for v in debuginfo.get('locals', []):
            name = v.get('name', None)
            value = v.get('value', None)
            if expected_locals.get(name, None) == value:
                expected_locals.pop(name)
        if len(expected_locals) == 0 and len(expected_breakpoints) == 0:
            return [
                {"status": "key_step", "index": 6},
                {"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_hit_file:
            return [{"status": "key_step", "index": 1}]
    elif event_type == "breakpoint_change":
        bp_file_path = message.get("path")
        bp_file_line = message.get("line")
        bp_condition = message.get("condition")
        bp_enabled = message.get("enabled")
        if bp_file_path != expected_hit_file:
            return None
        if bp_file_line == 17 and bp_enabled and bp_condition == expected_breakpoints.get("17"):
            return [{"status": "key_step", "index": 2}]
        elif bp_file_line == 19 and bp_enabled and bp_condition == expected_breakpoints.get("19"):
            return [{"status": "key_step", "index": 3}]
        elif bp_file_line == 22 and bp_enabled and bp_condition == expected_breakpoints.get("22"):
            return [{"status": "key_step", "index": 4}]
    elif event_type == "debug_session_start":
        program = message.get("program")
        if program == expected_program:
            return [{"status": "key_step", "index": 5}]
    return None