Task configuration:
{"task_id": "15", "task_name": "add_tag_to_multiple_notes", "description": "\u68c0\u67e5Joplin\u4e2d\u662f\u5426\u7ed9\u591a\u4e2a\u6307\u5b9a\u7b14\u8bb0\u6dfb\u52a0\u4e86\u540c\u4e00\u4e2a\u6807\u7b7e", "application_info": {"name": "joplin", "executable_path": "/workspace/PC-Canary/apps/joplin/Joplin-3.3.12.AppImage", "args": ["--env", "dev", "--profile", "/workspace/PC-Canary/apps/joplin/joplindev-desktop"]}, "task_parameters": {"note_names": ["note1", "note2", "note3"], "tag_name": "test_tag"}, "instruction_template": "\u5728Joplin\u4e2d\u7ed9\u4ee5\u4e0b\u7b14\u8bb0\u6dfb\u52a0\u6807\u7b7e ${tag_name}\uff1a${note_names}", "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, "preconditions": {"joplin_running": true}, "events": {"evaluate_on_completion": {"description": "\u5728\u4efb\u52a1\u7ed3\u675f\u65f6\u5224\u65ad\u6240\u6709\u6307\u5b9a\u7b14\u8bb0\u662f\u5426\u90fd\u5305\u542b\u6307\u5b9a\u6807\u7b7e", "is_key_step": true, "key_step_index": 1, "key_step_name": "\u591a\u7b14\u8bb0\u6807\u7b7e\u6dfb\u52a0\u68c0\u67e5\u5b8c\u6210"}}, "mcp_servers": [{"command": "/home/user/.local/bin/uv", "args": ["--directory", "/home/user/Desktop/joplin-mcp-server", "run", "src/mcp/joplin_mcp.py"]}]}

Hook script:
// 使用 Joplin 插件 API 来处理通信
joplin.plugins.register({
    onStart: async function() {
        console.log('Joplin plugin started');
        
        // 监听 evaluate 事件
        socket.on("evaluate", async () => {
            console.log('Task15_addTagToMultipleNotes---------------------------');
            try {
                // 获取所有笔记
                const notes = await joplin.data.get(['notes']);
                const noteList = notes.items;
                
                // 获取笔记及其标签
                const notesWithTags = await Promise.all(noteList.map(async note => {
                    try {
                        // 获取笔记的标签
                        const tags = await joplin.data.get(['notes', note.id, 'tags']);
                        return {
                            title: note.title,
                            tags: tags.items.map(tag => tag.title)
                        };
                    } catch (error) {
                        console.error(`Error getting tags for note ${note.title}:`, error);
                        return {
                            title: note.title,
                            tags: []
                        };
                    }
                }));
                
                // 使用 postMessage 发送评估消息
                window.postMessage({
                    "event_type": "evaluate_on_completion",
                    "message": "当前笔记列表: " + notesWithTags.map(note => note.title).join(', '),
                    "data": {
                        "notes": notesWithTags
                    }
                }, '*');
            } catch (error) {
                console.error('Error in evaluate handler:', error);
            }
        });
    }
});

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

"""
joplin给多个笔记添加标签的事件处理器
负责处理钩子脚本产生的事件并更新评估指标
"""

import json
import os
import sys
from typing import Dict, Any, Optional, List

def load_config_defaults() -> Dict[str, Any]:
    """从config.json加载默认参数值"""
    config_path = os.path.join(os.path.dirname(__file__), 'config.json')
    with open(config_path, 'r', encoding='utf-8') as f:
        config = json.load(f)
    return config.get('task_parameters', {})

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.get('message'))
    
    if event_type == "evaluate_on_completion":
        data = message.get("data", {})
        notes = data.get("notes", [])
        
        # 从config.json加载默认值
        config_defaults = load_config_defaults()
        expected_notes = task_parameter.get('note_names', config_defaults.get('note_names', []))
        expected_tag = task_parameter.get('tag_name', config_defaults.get('tag_name'))
        
        # 检查每个笔记是否都存在
        missing_notes = []
        notes_without_tag = []
        
        for note_name in expected_notes:
            target_note = next((note for note in notes if note.get("title") == note_name), None)
            if not target_note:
                missing_notes.append(note_name)
            elif expected_tag not in target_note.get("tags", []):
                notes_without_tag.append(note_name)
        
        if missing_notes:
            return [{"status": "error", "type": "evaluate_on_completion", "message": f"以下笔记不存在：{', '.join(missing_notes)}"}]
        
        if notes_without_tag:
            return [{"status": "error", "type": "evaluate_on_completion", "message": f"以下笔记未添加标签 {expected_tag}：{', '.join(notes_without_tag)}"}]
        
        return [
            {"status": "key_step", "index": 1},
            {"status": "success", "reason": f"成功给所有笔记添加了标签：{expected_tag}"}
        ]
    return None

if __name__ == '__main__':
    # 从标准输入读取评估数据
    input_data = sys.stdin.read()
    data = json.loads(input_data)
    
    # 评估结果
    result = message_handler(data, sys.stderr, load_config_defaults())
    
    # 输出评估结果
    if result:
        print(json.dumps(result, ensure_ascii=False))