diff --git a/src/main/db/sqlite.ts b/src/main/db/sqlite.ts index 1b77a32..004607d 100644 --- a/src/main/db/sqlite.ts +++ b/src/main/db/sqlite.ts @@ -233,7 +233,7 @@ export async function initDatabase(): Promise { _hasFTS5 = true; } catch { _hasFTS5 = false; - console.warn('[sqlite] FTS5 不可用,记忆搜索降级为 LIKE 模式'); + // FTS5 不可用时降级为 LIKE 搜索,状态通过 _hasFTS5 跟踪 } // 写入一次确保文件存在 diff --git a/src/main/tool-handlers.ts b/src/main/tool-handlers.ts index b64cdaf..6097129 100644 --- a/src/main/tool-handlers.ts +++ b/src/main/tool-handlers.ts @@ -641,11 +641,14 @@ export async function handleAppendFile(params: { path: string; content: string; if (!allowed.ok) return { success: false, error: allowed.reason }; const prefix = params.newline !== false ? '\n' : ''; + sendLog('info', `➕ append_file`, `${filePath} (${params.content.length} chars)`); await fs.appendFile(filePath, prefix + params.content, 'utf-8'); const stat = await fs.stat(filePath); + sendLog('success', `➕ append_file 完成`, `${stat.size}B`); return { success: true, path: filePath, newSize: stat.size }; } catch (err) { + sendLog('error', `➕ append_file 失败`, (err as Error).message); return { success: false, error: (err as Error).message }; } } @@ -674,9 +677,12 @@ export async function handleEditFile(params: { path: string; old_text: string; n newContent = content.replace(params.old_text, params.new_text); } + sendLog('info', `✂️ edit_file`, `${filePath} (${replaceCount} 处替换)`); await fs.writeFile(filePath, newContent, 'utf-8'); + sendLog('success', `✂️ edit_file 完成`, `${newContent.length}B`); return { success: true, path: filePath, replaceCount, newSize: newContent.length }; } catch (err) { + sendLog('error', `✂️ edit_file 失败`, (err as Error).message); return { success: false, error: (err as Error).message }; } } @@ -770,6 +776,7 @@ export async function handleDownloadFile(params: { url: string; destination: str return { success: false, error: '仅支持 http/https 协议' }; } + sendLog('info', `⬇️ download_file`, `${params.url} → ${destPath}`); const resp = await fetch(params.url, { signal: AbortSignal.timeout(60000) }); @@ -784,6 +791,7 @@ export async function handleDownloadFile(params: { url: string; destination: str await fs.mkdir(destDir, { recursive: true }); await fs.writeFile(destPath, buf); + sendLog('success', `⬇️ download_file 完成`, `${buf.length} bytes`); return { success: true, url: params.url, @@ -792,6 +800,7 @@ export async function handleDownloadFile(params: { url: string; destination: str content_type: resp.headers.get('content-type') || 'unknown' }; } catch (err) { + sendLog('error', `⬇️ download_file 失败`, (err as Error).message); return { success: false, error: (err as Error).message }; } } diff --git a/src/renderer/components/chat-area.ts b/src/renderer/components/chat-area.ts index 506647d..e746b1c 100644 --- a/src/renderer/components/chat-area.ts +++ b/src/renderer/components/chat-area.ts @@ -189,14 +189,24 @@ function renderToolCallCard(tc: ToolCallRecord): string { const icons: Record = { read_file: '📄', write_file: '✏️', list_directory: '📁', search_files: '🔍', create_directory: '📂', delete_file: '🗑️', run_command: '💻', - web_search: '🔍', web_fetch: '🌐', git: '🔖', - memory_search: '🧠', memory_add: '💾', session_list: '📋', session_read: '📖' + move_file: '📦', copy_file: '📋', web_fetch: '🌐', append_file: '➕', + edit_file: '✂️', get_file_info: 'ℹ️', tree: '🌳', download_file: '⬇️', + diff_files: '🔀', replace_in_files: '🔄', read_multiple_files: '📚', + git: '🔖', compress: '🗜️', web_search: '🔍', + memory_search: '🧠', memory_add: '💾', session_list: '📋', session_read: '📖', spawn_task: '🤖', + browser_open: '🌐', browser_screenshot: '📸', browser_evaluate: '⚡', browser_extract: '📰', + browser_click: '👆', browser_type: '⌨️', browser_scroll: '↕️', browser_close: '❌' }; const names: Record = { read_file: '读取文件', write_file: '写入文件', list_directory: '列出目录', search_files: '搜索文件', create_directory: '创建目录', delete_file: '删除文件', run_command: '执行命令', - web_search: '联网搜索', web_fetch: '网页抓取', git: 'Git 操作', - memory_search: '搜索记忆', memory_add: '添加记忆', session_list: '会话列表', session_read: '读取会话' + move_file: '移动文件', copy_file: '复制文件', web_fetch: '网页抓取', append_file: '追加文件', + edit_file: '编辑文件', get_file_info: '文件信息', tree: '目录树', download_file: '下载文件', + diff_files: '文件对比', replace_in_files: '批量替换', read_multiple_files: '批量读取', + git: 'Git 操作', compress: '压缩/解压', web_search: '联网搜索', + memory_search: '搜索记忆', memory_add: '添加记忆', session_list: '会话列表', session_read: '读取会话', spawn_task: '子代理委派', + browser_open: '打开网页', browser_screenshot: '浏览器截图', browser_evaluate: '执行JS', browser_extract: '提取内容', + browser_click: '点击元素', browser_type: '输入文本', browser_scroll: '滚动页面', browser_close: '关闭浏览器' }; const statusLabels: Record = { pending: '⏳ 等待确认', running: '🔄 执行中', success: '✅ 完成', error: '❌ 失败', cancelled: '🚫 已取消' @@ -211,6 +221,11 @@ function renderToolCallCard(tc: ToolCallRecord): string { if (args.path) paramsHtml += `
路径: ${escapeHtml(String(args.path))}
`; if (args.command) paramsHtml += `
命令: ${escapeHtml(String(args.command))}
`; if (args.query) paramsHtml += `
查询: ${escapeHtml(String(args.query))}
`; + if (args.url) paramsHtml += `
URL: ${escapeHtml(String(args.url))}
`; + if (args.source) paramsHtml += `
源: ${escapeHtml(String(args.source))}
`; + if (args.destination) paramsHtml += `
目标: ${escapeHtml(String(args.destination))}
`; + if (args.selector) paramsHtml += `
选择器: ${escapeHtml(String(args.selector))}
`; + if (args.action) paramsHtml += `
操作: ${escapeHtml(String(args.action))}
`; let resultHtml = ''; if (tc.result) { @@ -248,6 +263,52 @@ function renderToolCallCard(tc: ToolCallRecord): string { if (stdout) resultHtml += `
${escapeHtml(stdout)}
`; if (stderr) resultHtml += `
${escapeHtml(stderr)}
`; resultHtml += `
exit: ${r.exitCode || 0} · ${r.duration || 0}ms
`; + } else if (tc.name === 'web_search' && r.results) { + const results = r.results as Array<{ title: string; url: string; snippet: string }>; + resultHtml = results.slice(0, 5).map((item, i) => + `
[${i + 1}] ${escapeHtml(item.title)}
${escapeHtml(item.url)}
` + ).join(''); + resultHtml += `
共 ${r.total || results.length} 条结果
`; + } else if (tc.name === 'web_fetch') { + const content = String(r.content || '').slice(0, 500); + resultHtml = `
${escapeHtml(content)}${(r.content as string || '').length > 500 ? '\n...' : ''}
`; + resultHtml += `
${r.length || content.length} chars
`; + } else if (tc.name === 'git' && r.action) { + if (r.commits) { + resultHtml = (r.commits as Array<{ hash: string; message: string }>).slice(0, 10).map(c => + `
${escapeHtml(c.hash)} ${escapeHtml(c.message)}
` + ).join(''); + } else if (r.branches) { + resultHtml = (r.branches as Array<{ name: string; current: boolean }>).map(b => + `
${b.current ? '→ ' : ''}${escapeHtml(b.name)}
` + ).join(''); + } else { + resultHtml = `
✅ ${r.action} 完成
`; + } + } else if (tc.name === 'memory_search' && r.results) { + const results = r.results as Array<{ type: string; content: string }>; + resultHtml = results.slice(0, 5).map(m => + `
${m.type === 'rule' ? '📏' : m.type === 'preference' ? '⚙️' : '📌'} ${escapeHtml(m.content).slice(0, 80)}
` + ).join(''); + } else if (tc.name === 'session_list' && r.sessions) { + const sessions = r.sessions as Array<{ title: string; messageCount: number }>; + resultHtml = sessions.slice(0, 5).map(s => + `
💬 ${escapeHtml(s.title)}
` + ).join(''); + } else if (tc.name?.startsWith('browser_')) { + if (tc.name === 'browser_screenshot' && r.png) { + resultHtml = `
📸 截图已生成
`; + } else if (tc.name === 'browser_extract') { + const text = String(r.text || '').slice(0, 300); + resultHtml = `
${escapeHtml(text)}
`; + resultHtml += `
链接: ${(r.links as unknown[])?.length || 0} 个
`; + } else if (r.title) { + resultHtml = `
📄 ${escapeHtml(r.title)}
`; + } else if (r.result) { + resultHtml = `
${escapeHtml(String(r.result).slice(0, 300))}
`; + } else { + resultHtml = `
✅ 完成
`; + } } else { resultHtml = `
✅ 操作成功
`; } diff --git a/src/renderer/main.ts b/src/renderer/main.ts index 3fb7781..e85c673 100644 --- a/src/renderer/main.ts +++ b/src/renderer/main.ts @@ -25,7 +25,7 @@ import { initMemoryManager } from './services/memory-manager.js'; import { restoreHeartbeat } from './components/settings-modal.js'; import { restoreCronTasks } from './services/cron-manager.js'; import { setToolEnabled, setRunCommandMode, refreshMCPTools } from './services/tool-registry.js'; -import { startAllMCPServers, stopAllMCPServers } from './services/mcp-client.js'; +import { startAllMCPServers } from './services/mcp-client.js'; import { initToolConfirmModal } from './components/tool-confirm-modal.js'; import { initWorkspacePanel } from './components/workspace-panel.js'; import { initLogPanel, addLog } from './services/log-service.js'; diff --git a/src/renderer/services/skill-manager.ts b/src/renderer/services/skill-manager.ts index e478d45..a842228 100644 --- a/src/renderer/services/skill-manager.ts +++ b/src/renderer/services/skill-manager.ts @@ -181,8 +181,6 @@ export function buildSkillContext(skills: Skill[]): string { return `【可用技能提示 — 以下技能曾成功完成过类似任务,可作为参考】\n\n${parts.join('\n\n')}\n\n提示:如果当前任务与某个技能匹配,可以参考其工具链步骤,但根据实际情况调整参数。`; } -/** - // ── 辅助函数 ── /** 生成工具步骤描述 */ @@ -202,6 +200,15 @@ function describeToolStep(t: ToolCallRecord): string { case 'create_directory': return `创建目录 ${args.path || ''}`; case 'memory_search': return `搜索记忆 "${args.query || ''}"`; case 'memory_add': return `添加记忆`; + case 'spawn_task': return `子代理委派`; + case 'browser_open': return `打开网页 ${args.url || ''}`; + case 'browser_screenshot': return `浏览器截图`; + case 'browser_evaluate': return `执行浏览器 JS`; + case 'browser_extract': return `提取页面内容`; + case 'browser_click': return `点击元素 ${args.selector || ''}`; + case 'browser_type': return `输入文本 ${args.selector || ''}`; + case 'browser_scroll': return `滚动页面`; + case 'browser_close': return `关闭浏览器`; default: return t.name; } }