Task configuration:
{"task_id": "task01_add_collection", "task_name": "add_collection", "description": "\u5728Zotero\u4e2d\u6dfb\u52a0\u5206\u7c7b", "application_info": {"type": "electron", "name": "zotero", "executable_path": "apps/zotero/app/scripts/build_and_run", "args": ["-r"]}, "task_parameters": {"collection_name": "virtualization"}, "instruction": "\u5728\u5df2\u7ecf\u6253\u5f00\u7684zotero\u7a97\u53e3 `MyLibrary`\u4e2d\u6dfb\u52a0\u5206\u7c7b`virtualization`", "preconditions": {"zotero_running": true}, "instruction_template": "\u5728\u5df2\u7ecf\u6253\u5f00\u7684zotero `MyLibrary` \u4e2d\u6dfb\u52a0\u5206\u7c7b\u4e3a `${collection_name}`", "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": 1, "events": {"collection_added": {"description": "\u6210\u529f\u6dfb\u52a0\u5206\u7c7b", "is_key_step": true, "key_step_index": 1, "key_step_name": "\u6210\u529f\u6dfb\u52a0\u5206\u7c7b"}}, "mcp_servers": [{"cwd": "/home/agent/zotero-mcp", "command": "uv", "args": ["run", "zotero-mcp"], "env": {"ZOTERO_LOCAL": "true"}}], "pre_process": {"command": "python", "args": ["/home/agent/agent-demo/PC-Canary/tests/tasks/zotero/task01_add_collection/pre_process.py"]}, "context_data": [{"from": "tests/context_data/zotero/task01_add_collection/Zotero", "to": "/home/agent/Zotero"}, {"from": "tests/context_data/zotero/task01_add_collection/.zotero", "to": "/home/agent/.zotero"}]}

Hook script:
// // hookClient.js
// const http = require('http');
// const { URL } = require('url');

// // 基础配置
// const HOOKER_SERVER_HOST = 'localhost';
// const HOOKER_SERVER_PORT = 5002;
// const REQUEST_TIMEOUT = 5000; // 5秒超时

// function sendRequest(path) {
//     return new Promise((resolve, reject) => {
//         const url = new URL(`http://${HOOKER_SERVER_HOST}:${HOOKER_SERVER_PORT}/${path}`);

//         const req = http.get(url, (res) => {
//             let data = '';

//             // 检查 HTTP 状态码
//             if (res.statusCode >= 400) {
//                 res.on('data', chunk => data += chunk);
//                 res.on('end', () => {
//                     try {
//                         const error = JSON.parse(data);
//                         reject(new Error(error.error || `HTTP ${res.statusCode}`));
//                     } catch (e) {
//                         reject(new Error(`HTTP ${res.statusCode}: ${data}`));
//                     }
//                 });
//                 return;
//             }

//             res.on('data', chunk => data += chunk);
//             res.on('end', () => {
//                 try {
//                     resolve(JSON.parse(data));
//                 } catch (err) {
//                     reject(new Error(`Invalid JSON response: ${data}`));
//                 }
//             });
//         }).on('error', reject);

//         // 设置请求超时
//         req.setTimeout(REQUEST_TIMEOUT, () => {
//             req.destroy();
//             reject(new Error('Request timeout'));
//         });
//     });
// }

// // 启用 Hook
// async function enableHook(hookName) {
//     return sendRequest(`call/${hookName}`);
// }

// // 禁用 Hook
// async function disableHook(hookName) {
//     return sendRequest(`stop/${hookName}`);
// }

// // 列出所有 Hook
// async function listHooks() {
//     return sendRequest('list');
// }

// // 执行主逻辑
// (async () => {
//     try {
//         // 先列出所有可用的 hooks
//         console.log('正在获取可用的 hooks...');
//         const listResult = await listHooks();
//         console.log('可用的 hooks:', listResult.data);

//         // 启用 hookAddCollection
//         console.log('\n正在启用 hookAddCollection...');
//         const enableResult = await enableHook('hookAddCollection');
//         console.log('启用结果:', enableResult);

//         // 验证 hook 状态
//         console.log('\n验证 hook 状态...');
//         const statusResult = await listHooks();
//         console.log('活跃的 hooks:', statusResult.data.activeHooks);

//         // 可选：稍后禁用 hook
//         // console.log('\n正在禁用 hookAddCollection...');
//         // const disableResult = await disableHook('hookAddCollection');
//         // console.log('禁用结果:', disableResult);

//     } catch (err) {
//         console.error('操作失败:', err.message);

//         // 提供更详细的错误信息
//         if (err.code === 'ECONNREFUSED') {
//             console.error('无法连接到 hook 服务器，请检查服务器是否正在运行');
//         } else if (err.message.includes('timeout')) {
//             console.error('请求超时，服务器可能响应缓慢');
//         }

//         // 非零退出码表示脚本执行失败
//         process.exit(1);
//     }
// })();

Evaluation handler:
import os
import json
import time
from typing import Dict, Any, Optional, Callable, List
import sqlite3
import platform
import shutil


def message_handler(message: Dict[str, Any], logger: Any, task_parameter: Dict[str, Any]) -> Optional[List[Dict[str, Any]]]:
    global _EVALUATOR, _CONFIG, _START_TIME
    event_type = message.get('event_type')
    if event_type == 'collection_added':
        if check_virtualization_collections():
            return [
                {"status": "key_step", "index": 1},
                {"status": "success", "reason": "虚拟化分类已成功添加"}
            ]
        else:
            return None
    else:
        return [
            {"status": "error", "type": "evaluate_on_completion", "message": "任务没有完成"}
        ]

# 根据操作系统找到 Zotero 数据库路径
def get_zotero_db_path():
    system = platform.system()
    home = os.path.expanduser("~")
    
    if system == 'Windows':
        return os.path.join(home, 'Zotero', 'zotero.sqlite')
    elif system == 'Darwin':  # macOS
        return os.path.join(home, 'Zotero', 'zotero.sqlite')
    elif system == 'Linux':
        return os.path.join(home, 'Zotero', 'zotero.sqlite')
    else:
        raise Exception("不支持的操作系统")

# 检查是否存在 virtualization 分类
def check_virtualization_collections():
    print("检查 virtualization 分类")
    try:
        # 获取原始数据库路径
        original_db_path = get_zotero_db_path()
        if not os.path.exists(original_db_path):
            print(f"Zotero 数据库文件未找到: {original_db_path}")
            return False
        
        # 创建临时数据库副本
        temp_db_path = original_db_path + ".temp"
        print(f"创建数据库临时副本: {temp_db_path}")
        shutil.copy2(original_db_path, temp_db_path)
        
        try:
            # 连接到临时数据库副本
            conn = sqlite3.connect(temp_db_path)
            cursor = conn.cursor()
            
            # 查询包含 virtualization 的分类
            cursor.execute("""
                SELECT COUNT(*)
                FROM collections
                WHERE collectionName LIKE '%virtualization%'
            """)
            
            count = cursor.fetchone()[0]
            conn.close()
            
            # 返回查询结果
            return count > 0
            
        except sqlite3.Error as e:
            print(f"查询数据库时出错: {e}")
            return False
            
        finally:
            # 无论查询是否成功，都删除临时数据库
            if os.path.exists(temp_db_path):
                try:
                    os.remove(temp_db_path)
                    print(f"已删除临时数据库副本")
                except OSError as e:
                    print(f"删除临时数据库时出错: {e}")
    
    except Exception as e:
        print(f"检查 virtualization 分类时出错: {e}")
        return False