diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 72182f4..34c267c 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -38,6 +38,32 @@ import { import { getAllowedDirs, getBlockedDirs, setAllowedDirs } from './tool-security.js'; import { startProcess, killProcess, getWorkspaceDir, setWorkspaceDir, listWorkspaceDir } from './workspace.js'; +/** 工具结果摘要(用于日志面板) */ +function summarizeResult(toolName: string, result: Record): string { + switch (toolName) { + case 'read_file': return `${result.path} (${result.lines} 行${result.truncated ? ', 已截断' : ''})`; + case 'write_file': return `${result.path} (${result.bytesWritten}B${result.created ? ', 新建' : ''})`; + case 'list_directory': return `${result.path} (${result.total} 项${result.truncated ? ', 已截断' : ''})`; + case 'search_files': return `"${result.query}" → ${result.total_matches} 匹配 / ${result.total_files} 文件`; + case 'create_directory': return String(result.path); + case 'delete_file': return String(result.path); + case 'move_file': return `${result.source} → ${result.destination}`; + case 'copy_file': return `${result.source} → ${result.destination}`; + case 'web_fetch': return `${result.status} | ${result.length} chars`; + case 'append_file': return `${result.path} (${result.newSize}B)`; + case 'edit_file': return `${result.path} (${result.replaceCount} 处替换)`; + case 'get_file_info': return `${result.name} (${result.type}, ${result.size}B)`; + case 'tree': return `${result.path} (${result.fileCount} 文件 / ${result.dirCount} 目录)`; + case 'download_file': return `${result.url} → ${result.destination} (${result.size}B)`; + case 'diff_files': return result.hasChanges ? `${result.file1} ↔ ${result.file2} (有差异)` : '无差异'; + case 'replace_in_files': return `${result.filesChanged} 文件 / ${result.totalReplacements} 处替换`; + case 'read_multiple_files': return `${result.total} 个文件`; + case 'git': return `${result.action} ✓`; + case 'compress': return `${result.action} → ${result.archive || result.destination}`; + default: return '完成'; + } +} + export function setupIPC(): void { ipcMain.handle('dialog:openFile', async (_, options?: { filters?: Array<{ name: string; extensions: string[] }> }) => { const result = await dialog.showOpenDialog(mainWindow!, { @@ -113,29 +139,37 @@ export function setupIPC(): void { ipcMain.handle('tool:execute', async (_, toolName: string, args: Record) => { try { sendLog('debug', `🔧 tool:execute ${toolName}`, JSON.stringify(args || {}).slice(0, 200)); + let result; switch (toolName) { - case 'read_file': return await handleReadFile(args as { path: string; encoding?: string; start_line?: number; end_line?: number }); - case 'write_file': return await handleWriteFile(args as { path: string; content: string; encoding?: string }); - case 'list_directory': return await handleListDir(args as { path: string; recursive?: boolean; max_depth?: number; include_hidden?: boolean; filter_extension?: string }); - case 'search_files': return await handleSearchFiles(args as { path: string; query: string; search_type?: string; case_sensitive?: boolean; max_results?: number; file_extensions?: string[] }); - case 'create_directory': return await handleCreateDir(args as { path: string }); - case 'delete_file': return await handleDeleteFile(args as { path: string; recursive?: boolean }); - case 'run_command': return await handleRunCommand(args as { command: string; cwd?: string; timeout?: number }); - case 'move_file': return await handleMoveFile(args as { source: string; destination: string }); - case 'copy_file': return await handleCopyFile(args as { source: string; destination: string; recursive?: boolean }); - case 'web_fetch': return await handleWebFetch(args as { url: string; max_chars?: number; extract_mode?: string }); - case 'append_file': return await handleAppendFile(args as { path: string; content: string; newline?: boolean }); - case 'edit_file': return await handleEditFile(args as { path: string; old_text: string; new_text: string; all?: boolean }); - case 'get_file_info': return await handleGetFileInfo(args as { path: string }); - case 'tree': return await handleTree(args as { path: string; max_depth?: number; include_hidden?: boolean }); - case 'download_file': return await handleDownloadFile(args as { url: string; destination: string }); - case 'diff_files': return await handleDiffFiles(args as { file1: string; file2: string; context_lines?: number }); - case 'replace_in_files': return await handleReplaceInFiles(args as { path: string; glob: string; old_text: string; new_text: string }); - case 'read_multiple_files':return await handleReadMultipleFiles(args as { paths: string[]; max_chars_per_file?: number }); - case 'git': return await handleGit(args as any); - case 'compress': return await handleCompress(args as { action: string; path: string; destination?: string; format?: string }); + case 'read_file': result = await handleReadFile(args as { path: string; encoding?: string; start_line?: number; end_line?: number }); break; + case 'write_file': result = await handleWriteFile(args as { path: string; content: string; encoding?: string }); break; + case 'list_directory': result = await handleListDir(args as { path: string; recursive?: boolean; max_depth?: number; include_hidden?: boolean; filter_extension?: string }); break; + case 'search_files': result = await handleSearchFiles(args as { path: string; query: string; search_type?: string; case_sensitive?: boolean; max_results?: number; file_extensions?: string[] }); break; + case 'create_directory': result = await handleCreateDir(args as { path: string }); break; + case 'delete_file': result = await handleDeleteFile(args as { path: string; recursive?: boolean }); break; + case 'run_command': return await handleRunCommand(args as { command: string; cwd?: string; timeout?: number }); // run_command 自身管理日志 + case 'move_file': result = await handleMoveFile(args as { source: string; destination: string }); break; + case 'copy_file': result = await handleCopyFile(args as { source: string; destination: string; recursive?: boolean }); break; + case 'web_fetch': result = await handleWebFetch(args as { url: string; max_chars?: number; extract_mode?: string }); break; + case 'append_file': result = await handleAppendFile(args as { path: string; content: string; newline?: boolean }); break; + case 'edit_file': result = await handleEditFile(args as { path: string; old_text: string; new_text: string; all?: boolean }); break; + case 'get_file_info': result = await handleGetFileInfo(args as { path: string }); break; + case 'tree': result = await handleTree(args as { path: string; max_depth?: number; include_hidden?: boolean }); break; + case 'download_file': result = await handleDownloadFile(args as { url: string; destination: string }); break; + case 'diff_files': result = await handleDiffFiles(args as { file1: string; file2: string; context_lines?: number }); break; + case 'replace_in_files': result = await handleReplaceInFiles(args as { path: string; glob: string; old_text: string; new_text: string }); break; + case 'read_multiple_files':result = await handleReadMultipleFiles(args as { paths: string[]; max_chars_per_file?: number }); break; + case 'git': result = await handleGit(args as any); break; + case 'compress': result = await handleCompress(args as { action: string; path: string; destination?: string; format?: string }); break; default: return { success: false, error: `未知工具: ${toolName}` }; } + // 结果日志 + if (result.success) { + sendLog('success', `🔧 ${toolName} ✓`, summarizeResult(toolName, result)); + } else { + sendLog('warn', `🔧 ${toolName} ✗`, (result.error || '未知错误').slice(0, 200)); + } + return result; } catch (err) { sendLog('error', `❌ tool:execute 异常: ${toolName}`, String(err)); return { success: false, error: (err as Error).message || '工具执行异常' }; diff --git a/src/main/tool-handlers.ts b/src/main/tool-handlers.ts index f056710..240cf3f 100644 --- a/src/main/tool-handlers.ts +++ b/src/main/tool-handlers.ts @@ -390,9 +390,12 @@ export async function handleMoveFile(params: { source: string; destination: stri const destCheck = checkPathAllowed(dest, 'write'); if (!destCheck.ok) return { success: false, error: destCheck.reason }; + sendLog('info', `📦 move_file`, `${src} → ${dest}`); await fs.rename(src, dest); + sendLog('success', `📦 move_file 完成`, dest); return { success: true, source: src, destination: dest }; } catch (err) { + sendLog('error', `📦 move_file 失败`, (err as Error).message); return { success: false, error: (err as Error).message }; } } @@ -407,6 +410,7 @@ export async function handleCopyFile(params: { source: string; destination: stri if (!destCheck.ok) return { success: false, error: destCheck.reason }; const stat = await fs.stat(src); + sendLog('info', `📋 copy_file`, `${src} → ${dest}${stat.isDirectory() ? ' (目录)' : ''}`); if (stat.isDirectory()) { await fs.cp(src, dest, { recursive: params.recursive !== false }); } else { @@ -414,8 +418,10 @@ export async function handleCopyFile(params: { source: string; destination: stri await fs.mkdir(destDir, { recursive: true }); await fs.copyFile(src, dest); } + sendLog('success', `📋 copy_file 完成`, dest); return { success: true, source: src, destination: dest, isDirectory: stat.isDirectory() }; } catch (err) { + sendLog('error', `📋 copy_file 失败`, (err as Error).message); return { success: false, error: (err as Error).message }; } } @@ -429,6 +435,7 @@ export async function handleWebFetch(params: { url: string; max_chars?: number; const maxChars = params.max_chars || 10000; + sendLog('info', `🌐 web_fetch`, `${url} (${maxChars} chars)`); const resp = await fetch(url, { headers: { 'User-Agent': 'MetonaOllama/1.0' }, signal: AbortSignal.timeout(15000) @@ -462,6 +469,7 @@ export async function handleWebFetch(params: { url: string; max_chars?: number; const truncated = text.length > maxChars; if (truncated) text = text.slice(0, maxChars); + sendLog('success', `🌐 web_fetch 完成`, `${resp.status} | ${text.length} chars${truncated ? ' (已截断)' : ''}`); return { success: true, url,