feat: 全面补充工具执行日志
- IPC 分发层:所有工具执行后自动记录成功/失败日志 - 新增 summarizeResult 函数:为每个工具生成可读的结果摘要 - move_file/copy_file/web_fetch:新增操作前后的详细日志 - 日志面板实时显示:工具名 + 结果摘要 + 错误详情 示例: 🔧 tool:execute move_file → 📦 move_file 完成 🔧 web_fetch ✓ → 200 | 5432 chars 🔧 edit_file ✗ → 未找到要替换的文本
This commit is contained in:
+54
-20
@@ -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, unknown>): 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<string, unknown>) => {
|
||||
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 || '工具执行异常' };
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user