Task configuration:
{"task_id": "22", "task_name": "add_pdf_attachment", "description": "\u5728Joplin\u4e2d\u7ed9\u6307\u5b9a\u7b14\u8bb0\u6dfb\u52a0PDF\u9644\u4ef6", "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": {"notebook_name": "TestNotebook", "note_title": "TestNote", "pdf_file_path": "/workspace/PC-Canary/apps/joplin/joplindev-desktop/mcptest-files/task22/test.pdf"}, "instruction_template": "\u5728Joplin\u4e2d\u5c06PDF\u6587\u4ef6 ${pdf_file_path} \u6dfb\u52a0\u5230\u7b14\u8bb0\u672c ${notebook_name} \u4e0b\u7684\u7b14\u8bb0 ${note_title} \u4e2d", "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\u65adPDF\u662f\u5426\u5df2\u6210\u529f\u6dfb\u52a0\u5230\u6307\u5b9a\u7b14\u8bb0\u4e2d", "is_key_step": true, "key_step_index": 1, "key_step_name": "PDF\u9644\u4ef6\u6dfb\u52a0\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('Task22_addPdfAttachment---------------------------');
            try {
                // 获取所有笔记本
                const notebooks = await joplin.data.get(['folders']);
                const notebookList = notebooks.items;
                
                // 获取所有笔记
                const notes = await joplin.data.get(['notes']);
                const noteList = notes.items;
                
                // 获取当前笔记本名称和笔记名称
                const currentNotebookName = notebookList.map(notebook => notebook.title);
                const currentNoteName = noteList.map(note => note.title);
                
                console.log('Current notebooks:', currentNotebookName);
                console.log('Current notes:', currentNoteName);
                
                const notebookMap = {};  // 存储笔记本ID和名称的对应关系
                notebookList.forEach(notebook => {
                    notebookMap[notebook.id] = notebook.title;
                });

                // 获取所有资源（附件）- 尝试获取更多字段
                const resources = await joplin.data.get(['resources'], {
                    fields: ['id', 'title', 'mime', 'filename', 'file_extension', 'updated_time']
                });
                
                console.log('All resources:', JSON.stringify(resources.items));

                // 准备一个空数组来收集笔记信息
                let notesWithAttachments = [];
                
                // 单独处理每个笔记，而不是使用Promise.all
                for (const note of noteList) {
                    try {
                        // 获取笔记详情，包括正文
                        const noteDetail = await joplin.data.get(['notes', note.id], { 
                            fields: ['id', 'title', 'body', 'parent_id'] 
                        });
                        const body = noteDetail.body || '';
                        
                        console.log(`Processing note: ${note.title}, ID: ${note.id}`);
                        
                        // 收集附件信息 - 尝试多种方法
                        let attachments = [];
                        
                        // 方法1: 使用API直接获取笔记的资源
                        try {
                            const noteResources = await joplin.data.get(['notes', note.id, 'resources'], {
                                fields: ['id', 'title', 'mime', 'filename', 'file_extension']
                            });
                            console.log(`Method 1 - Resources for note "${note.title}":`, JSON.stringify(noteResources.items));
                            attachments = noteResources.items;
                        } catch (error) {
                            console.error(`Method 1 - Error getting resources for note ${note.title}:`, error);
                        }
                        
                        // 方法2: 从笔记内容中提取资源ID
                        if (!attachments || attachments.length === 0) {
                            try {
                                // 在Markdown中，资源引用通常是 ![name](:/resource_id) 或 [name](:/resource_id)
                                const resourceRegex = /\(:\/([a-f0-9]+)\)/g;
                                const matches = body.matchAll(resourceRegex);
                                let resourceIds = [];
                                
                                for (const match of matches) {
                                    resourceIds.push(match[1]);
                                }
                                
                                if (resourceIds.length > 0) {
                                    console.log(`Method 2 - Found resource IDs in note "${note.title}":`, resourceIds);
                                    
                                    // 获取这些资源的详细信息
                                    const matchedResources = resources.items.filter(res => 
                                        resourceIds.includes(res.id)
                                    );
                                    
                                    console.log(`Method 2 - Matched resources:`, JSON.stringify(matchedResources));
                                    attachments = matchedResources;
                                }
                            } catch (error) {
                                console.error(`Method 2 - Error extracting resources from note ${note.title}:`, error);
                            }
                        }
                        
                        // 检查PDF附件
                        const hasPdf = attachments.some(attachment => 
                            attachment.mime === 'application/pdf' ||
                            (attachment.file_extension && attachment.file_extension.toLowerCase() === 'pdf') ||
                            (attachment.filename && attachment.filename.toLowerCase().endsWith('.pdf'))
                        );
                        
                        console.log(`Note "${note.title}" has PDF: ${hasPdf}`);
                        
                        // 添加笔记信息到结果数组
                        notesWithAttachments.push({
                            note_title: note.title,
                            note_id: note.id,
                            parent_id: note.parent_id,
                            parent_name: notebookMap[note.parent_id],
                            attachments: attachments,
                            has_pdf: hasPdf  // 添加一个明确的标志，指示是否有PDF附件
                        });
                    } catch (error) {
                        console.error(`Error processing note ${note.title}:`, error);
                        notesWithAttachments.push({
                            note_title: note.title,
                            parent_id: note.parent_id,
                            parent_name: notebookMap[note.parent_id],
                            attachments: [],
                            has_pdf: false
                        });
                    }
                }

                // 使用 postMessage 发送评估消息
                window.postMessage({
                    "event_type": "evaluate_on_completion",
                    "message": "当前笔记本列表: " + currentNotebookName.join(', ') + "\n当前笔记列表: " + currentNoteName.join(', '),
                    "data": {
                        "notebooks": currentNotebookName,
                        "notebook_notes": notesWithAttachments
                    }
                }, '*');
                console.log('Evaluation result sent via postMessage');
            } catch (error) {
                console.error('Error in evaluate handler:', error);
                // 即使出错也发送消息，确保评估流程继续
                window.postMessage({
                    "event_type": "evaluate_on_completion",
                    "message": "获取笔记信息时出错: " + error.message,
                    "data": {
                        "notebooks": [],
                        "notebook_notes": []
                    }
                }, '*');
            }
        });
    }
}); 

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

"""
joplin添加PDF附件的事件处理器
负责处理钩子脚本产生的事件并更新评估指标
"""

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

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", {})
        notebook_list = data.get("notebooks", [])
        notebook_notes = data.get("notebook_notes", [])
        
        expected_notebook = task_parameter.get('notebook_name', "TestNotebook")
        expected_note_title = task_parameter.get('note_title', "TestNote")
        
        # 获取期望的PDF文件名 - 只获取基本文件名
        pdf_file_path = task_parameter.get('pdf_file_path', "")
        expected_pdf_filename = os.path.basename(pdf_file_path)
        logger.info(f"预期PDF文件名: {expected_pdf_filename}")
        
        # 检查笔记本是否存在
        if expected_notebook not in notebook_list:
            return [{"status": "error", "type": "evaluate_on_completion", "message": f"笔记本 {expected_notebook} 不存在"}]
        
        # 获取指定笔记本下的所有笔记
        notebook_notes_in_target = [
            note for note in notebook_notes
            if note.get("parent_name") == expected_notebook
        ]
        
        # 查找目标笔记
        target_note = None
        for note in notebook_notes_in_target:
            if note.get("note_title") == expected_note_title:
                target_note = note
                break
        
        if not target_note:
            return [{"status": "error", "type": "evaluate_on_completion", 
                    "message": f"在笔记本 {expected_notebook} 下没有找到标题为 {expected_note_title} 的笔记"}]
        
        # 检查笔记是否有PDF附件
        has_pdf = target_note.get("has_pdf", False)
        attachments = target_note.get("attachments", [])
        
        # 如果找到了PDF附件，返回成功结果
        if has_pdf:
            # 简化返回数据
            return [
                {"status": "key_step", "index": 1},
                {"status": "success", "reason": f"成功将PDF文件添加到笔记本 {expected_notebook} 下的笔记 {expected_note_title} 中"}
            ]
        else:
            return [{"status": "error", "type": "evaluate_on_completion", 
                    "message": f"在笔记 {expected_note_title} 中没有找到PDF附件"}]
    return None