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:
OpenClaw Agent
2026-04-16 10:09:09 +08:00
parent 5085f59e17
commit 7c22405386
2 changed files with 62 additions and 20 deletions
+54 -20
View File
@@ -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 || '工具执行异常' };