Task configuration:
{"task_id": "13", "task_name": "add_layer_notes", "description": "\u5728QGIS\u4e2d\u4e3a\u6307\u5b9a\u56fe\u5c42\u6dfb\u52a0\u6807\u6ce8", "application_info": {"name": "QGIS", "executable_path": "/home/agent/qgis/QGIS/build-master/output/bin/qgis", "args": []}, "mcp_servers": [{"command": "python", "args": ["/home/agent/agent-demo/PC-Canary/tests/context_data/QGIS/qgis_config/qgis_mcp/qgis_mcp_server.py"]}], "context_data": [{"from": "tests/context_data/QGIS/qgis_sample_data", "to": "/home/agent/testbed/QGIS"}, {"from": "tests/context_data/QGIS/qgis_config", "to": "/home/agent/.local/share/QGIS"}], "task_parameters": {"LayerName": "lakes", "notes": "this is a test note"}, "instruction_template": "\u5728QGIS\u4e2d\u4e3a\u56fe\u5c42${LayerName}\u6dfb\u52a0\u6807\u6ce8(add layer note)\uff0c\u5185\u5bb9\u4e3a${notes}", "evaluation_setup": {"scripts": [{"role": "hook", "path": "hooker.js"}, {"role": "handler", "path": "handler.py"}], "timeout": 180}, "total_key_steps": 2, "events": {"script_initialized": {"description": "\u94a9\u5b50\u811a\u672c\u521d\u59cb\u5316", "is_key_step": false}, "set_function_found": {"description": "\u627e\u5230\u6dfb\u52a0\u56fe\u5c42\u6807\u6ce8\u51fd\u6570", "is_key_step": false}, "layer_set": {"description": "\u68c0\u6d4b\u5230\u5f85\u6dfb\u52a0\u6807\u6ce8\u7684\u56fe\u5c42\u540d\u79f0", "is_key_step": true, "key_step_index": 1, "key_step_name": "\u6b63\u5728\u64cd\u4f5c\u9884\u671f\u56fe\u5c42"}, "notes_set": {"description": "\u9884\u671f\u6807\u6ce8\u5df2\u8bbe\u7f6e", "is_key_step": true, "key_step_index": 2, "key_step_name": "\u8bbe\u7f6e\u4e86\u9884\u671f\u6807\u6ce8"}, "error": {"description": "\u6765\u81ea\u811a\u672c\u7684\u9519\u8bef\u4e8b\u4ef6", "is_key_step": false}}}

Hook script:

// QGIS添加图层注解钩子脚本
// 用于监听QGIS的添加图层注解操作并检测相关参数

(function () {
    // 脚本常量设置
    const SYMBOL_NAME = "_ZN18QgsLayerNotesUtils13setLayerNotesEP11QgsMapLayerRK7QString"; // QgsLayerNotesUtils::setLayerNotes函数的符号

    // 向评估系统发送事件
    function sendEvent(eventType, data = {}) {
        const payload = {
            event: eventType,
            ...data,
            timestamp: new Date().getTime()
        };
        send(payload);
    }

    const HEADER_SIZE = 24;
    const SIZE_OFFSET = 4;

    function qstringToString(qstr) {
        try {
            const d = qstr.readPointer();                         // QString::d
            const len = d.add(SIZE_OFFSET).readU32();             // QStringData::size
            const data = d.add(HEADER_SIZE);                      // first UTF‑16 char
            return Memory.readUtf16String(data, len);
        } catch (error) {
            console.log("解析QString失败:", error);
            return "";
        }
    }
    
    // 从HTML中提取纯文本内容
    function extractTextFromHtml(html) {
        try {
            // 提取<p>标签中的内容
            const matches = html.match(/<p[^>]*>(.*?)<\/p>/g);
            if (matches && matches.length > 0) {
                // 提取所有<p>标签中的文本并合并
                const textContents = matches.map(p => {
                    // 移除所有HTML标签
                    return p.replace(/<[^>]*>/g, '');
                });
                return textContents.join("\n");
            }
            
            // 如果没有找到<p>标签，则移除所有标签返回纯文本
            return html.replace(/<[^>]*>/g, '');
        } catch (error) {
            console.log("提取文本失败:", error);
            return html;
        }
    }

    // 初始化钩子并立即执行
    function initHook() {
        sendEvent("script_initialized", {
            message: "QGIS图层注解监控脚本已启动"
        });

        // 查找setLayerNotes函数
        let setNotesAddr = Module.findExportByName(null, SYMBOL_NAME);

        // 如果没找到，尝试扫描所有加载的模块
        if (!setNotesAddr) {
            sendEvent("function_search_start", {
                message: "正在查找QgsLayerNotesUtils::setLayerNotes函数..."
            });

            // 遍历模块
            Process.enumerateModules({
                onMatch: function (module) {
                    if (module.name.includes("qgis_core")) {
                        sendEvent("module_found", {
                            module_name: module.name,
                            base_address: module.base.toString()
                        });

                        // 在qgis_core模块中查找符号
                        const symbol = module.findExportByName(SYMBOL_NAME);
                        if (symbol) {
                            setNotesAddr = symbol;
                        }
                    }
                },
                onComplete: function () { }
            });

            // 如果仍未找到，报告错误
            if (!setNotesAddr) {
                sendEvent("error", {
                    error_type: "function_not_found",
                    message: "无法找到QgsLayerNotesUtils::setLayerNotes函数"
                });
                return;
            }
        }

        // 报告找到函数
        sendEvent("set_function_found", {
            address: setNotesAddr.toString(),
            message: "找到QgsLayerNotesUtils::setLayerNotes函数"
        });
        
        // 安装钩子
        Interceptor.attach(setNotesAddr, {
            onEnter: function(args) {
                try {
                    // 获取参数：layer和notes(由于是类调用吗，不存在this指针)
                    // args[0]是QgsLayer的指针，args[1]是QString的指针
                    const layerPtr = args[0];  
                    const notesQString = args[1];
                    console.log("添加图层注解,参数为:", layerPtr);
                    if (!layerPtr.isNull()) {
                        // 获取图层名称
                        const nameQString = layerPtr.add(0x20);
                        console.log("图层名称指针:", nameQString);
                        const layerName = qstringToString(nameQString);

                        console.log("添加图层注解,图层名称为:", layerName);

                        // 发送图层名称事件
                        sendEvent("layer_set", {
                            name: layerName,
                            message: `检测到添加注解的图层,名称为:${layerName}`
                        });
                        
                        // 获取注解内容
                        const notesHtml = qstringToString(notesQString);
                        const notesText = extractTextFromHtml(notesHtml);
                        
                        console.log("添加的注解内容为:", notesText);
                        console.log("原始HTML:", notesHtml);

                        // 发送注解内容事件
                        sendEvent("notes_set", {
                            layer: layerName,
                            notes: notesText,
                            notes_html: notesHtml,
                            message: `检测到为图层 ${layerName} 添加注解: ${notesText}`
                        });
                    }
                } catch (error) {
                    sendEvent("error", {
                        error_type: "hook_execution_error",
                        message: `执行钩子时出错: ${error.message}`,
                        stack: error.stack
                    });
                }
            }
        });

        sendEvent("hook_installed", {
            message: "钩子安装完成，等待添加图层注解操作..."
        });
    }

    // 立即执行钩子初始化
    initHook();
})();

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

"""
QGIS添加图层注解事件处理器
负责处理钩子脚本产生的事件并更新评估指标
"""

from typing import Dict, Any, Optional, List

def message_handler(message: Dict[str, Any], logger: Any, task_parameter: Dict[str, Any]) -> Optional[List[Dict[str, Any]]]:
    """
    处理从钩子脚本接收的消息
    
    Args:
        message: Frida消息对象
        logger: 日志记录器
        task_parameter: 任务参数
        
    Returns:
        Optional[List[Dict[str, Any]]]: 如果任务状态有更新返回状态列表，否则返回None
    """

    # 处理消息
    if message.get('type') == 'send' and 'payload' in message:
        payload = message['payload']
        
        # 检查是否包含事件类型
        if 'event' in payload:
            event_type = payload['event']
            logger.debug(f"接收到事件: {event_type}")
            
            # 处理特定事件
            if event_type == "script_initialized":
                logger.info(f"钩子脚本初始化: {payload.get('message', '')}")
                return None
                
            elif event_type == "set_function_found":
                logger.info(f"找到添加图层注解函数: {payload.get('address', '')}")
                return None
                
            elif event_type == "layer_set":
                layer_name = payload.get("name", "")
                logger.info(f"检测到添加图层注解: {layer_name}")
                
                # 检查图层名称是否匹配预期
                expected_layer_name = task_parameter.get("LayerName", "")
                logger.debug(f"比较图层名称 - 检测到: {layer_name}, 预期: {expected_layer_name}")
                
                # 判断检测到的图层名称是否与预期相符
                if expected_layer_name.lower() in layer_name.lower():
                    logger.info("添加注解的图层名称匹配预期目标!")                    
                    # 返回关键步骤
                    return [{"status": "key_step", "index": 1, "name": "正在操作预期图层"}]
                else:
                    logger.info(f"添加注解的图层名称与预期不符。预期: {expected_layer_name}, 实际: {layer_name}")
                
            elif event_type == "notes_set":
                layer_name = payload.get("layer", "")
                notes_text = payload.get("notes", "")
                notes_html = payload.get("notes_html", "")
                
                logger.info(f"检测到为图层 {layer_name} 添加注解: {notes_text}")
                
                # 检查图层名称和注解内容是否匹配预期
                expected_layer_name = task_parameter.get("LayerName", "")
                expected_notes = task_parameter.get("notes", "")
                
                # 清理和比较注解内容
                detected_notes = notes_text.strip().lower()
                expected_notes = expected_notes.strip().lower()
                
                # 判断检测到的注解内容是否与预期相符
                layer_match = expected_layer_name.lower() in layer_name.lower()
                notes_match = expected_notes in detected_notes
                
                if layer_match and notes_match:
                    logger.info(f"添加的图层注解匹配预期! 图层: {layer_name}, 注解: {notes_text}")                    
                    # 返回关键步骤和成功状态
                    return [
                        {"status": "key_step", "index": 2, "name": "设置了预期标注"},
                        {"status": "success", "reason": f"成功为图层 {layer_name} 添加注解: {notes_text}"}
                    ]
                else:
                    if not layer_match:
                        logger.info(f"图层与预期不符。预期: {expected_layer_name}, 实际: {layer_name}")
                    if not notes_match:
                        logger.info(f"注解内容与预期不符。预期: {expected_notes}, 实际: {detected_notes}")
                
            elif event_type == "error":
                error_type = payload.get("error_type", "unknown")
                error_message = payload.get("message", "未知错误")
                logger.error(f"钩子脚本错误 ({error_type}): {error_message}")
                
                # 返回错误状态
                return [{
                    "status": "error",
                    "type": error_type,
                    "message": error_message
                }]
                
    elif message.get('type') == 'error':
        stack = message.get('stack', '')
        logger.error(f"钩子脚本错误: {stack}")
        
        # 返回错误状态
        return [{
            "status": "error",
            "type": "script_error",
            "message": "钩子脚本执行错误",
            "stack_trace": stack
        }]
    
    return None