refactor: 删除append_file工具,功能合并入write_file(mode=append),工具数39→38

This commit is contained in:
thzxx
2026-06-10 14:42:25 +08:00
parent 1e89f52d42
commit e2bd511489
9 changed files with 15 additions and 59 deletions
+1 -1
View File
@@ -35,7 +35,7 @@
| | 功能 | 说明 |
|:---:|:---|:---|
| 🤖 | **ReAct Agent Loop** | 始终开启的唯一对话模式。Thought → Action → Observation → Reflection 完整循环,最大 85 轮(可配置),自动重试 2 次,工具去重,并行/链式执行 |
| 🔧 | **39 个内置工具** | 文件系统 · 命令执行 · 联网搜索 · 浏览器控制 · Git · 记忆 · 技能 · 会话 · 子代理 |
| 🔧 | **38 个内置工具** | 文件系统16个) · 命令执行 · 联网搜索 · 浏览器控制9个) · Git · 记忆 · 会话 · 子代理 |
| 🧠 | **智能记忆系统** | 三类记忆(fact / preference / rule),FTS5 全文搜索 + 向量语义搜索,写入前安全扫描,容量 500 条,90 天衰减 |
| 📋 | **自定义文件** | SOUL.md(人格,不可压缩)+ AGENT.md(行为准则)+ USER.md(用户画像),工作空间优先,内置默认 fallback |
| 🎯 | **技能自动生成 v1.1** | 工具调用链自动提取 → 语义匹配 → 参数自优化 → 未使用衰减 → 技能链合并,越用越精准 |
+1 -4
View File
@@ -37,7 +37,6 @@ import {
handleCopyFile,
handleWebFetch,
handleWebSearch,
handleAppendFile,
handleEditFile,
handleGetFileInfo,
handleTree,
@@ -67,7 +66,6 @@ function summarizeResult(toolName: string, result: Record<string, unknown>): str
case 'copy_file': return `${result.source}${result.destination}`;
case 'web_fetch': return `${result.status} | ${result.length} chars`;
case 'web_search': return `"${result.query}" → ${result.total} 条结果`;
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} 目录)`;
@@ -180,8 +178,7 @@ export async function setupIPC(): Promise<void> {
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 'web_search': result = await handleWebSearch(args as { query: string; max_results?: number }); 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 'edit_file': result = await handleEditFile(args as { path: string; old_text: string; new_text: string; all?: boolean; use_regex?: 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;
-21
View File
@@ -450,27 +450,6 @@ export async function handleDeleteFile(params: { path: string; recursive?: boole
}
}
/** 当前工具命令进程(用于用户手动终止) */
export async function handleAppendFile(params: { path: string; content: string; newline?: boolean }): Promise<ToolResult> {
try {
const filePath = resolvePath(params.path);
const allowed = checkPathAllowed(filePath, 'write');
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 };
}
}
export async function handleEditFile(params: { path: string; old_text: string; new_text: string; all?: boolean; use_regex?: boolean }): Promise<ToolResult> {
try {
const filePath = resolvePath(params.path);
+2 -2
View File
@@ -235,7 +235,7 @@ function renderToolCallCard(tc: ToolCallRecord): string {
const icons: Record<string, string> = {
read_file: '📄', write_file: '✏️', list_directory: '📁',
search_files: '🔍', create_directory: '📂', delete_file: '🗑️', run_command: '💻',
move_file: '📦', copy_file: '📋', web_fetch: '🌐', append_file: '',
move_file: '📦', copy_file: '📋', web_fetch: '🌐',
edit_file: '✂️', get_file_info: '️', tree: '🌳', download_file: '⬇️',
diff_files: '🔀', replace_in_files: '🔄', read_multiple_files: '📚',
git: '🔖', compress: '🗜️', web_search: '🔍',
@@ -246,7 +246,7 @@ function renderToolCallCard(tc: ToolCallRecord): string {
const names: Record<string, string> = {
read_file: '读取文件', write_file: '写入文件', list_directory: '列出目录',
search_files: '搜索文件', create_directory: '创建目录', delete_file: '删除文件', run_command: '执行命令',
move_file: '移动文件', copy_file: '复制文件', web_fetch: '网页抓取', append_file: '追加文件',
move_file: '移动文件', copy_file: '复制文件', web_fetch: '网页抓取',
edit_file: '编辑文件', get_file_info: '文件信息', tree: '目录树', download_file: '下载文件',
diff_files: '文件对比', replace_in_files: '批量替换', read_multiple_files: '批量读取',
git: 'Git 操作', compress: '压缩/解压', web_search: '联网搜索',
+3 -3
View File
@@ -834,7 +834,7 @@ function renderToolCalls(): void {
</div>
<div class="ws-idle-divider"></div>
<div class="ws-idle-desc">AI 对话中自动调用工具,结果在此展示</div>
<div class="ws-idle-hint">共 39 个内置工具 · MCP 动态扩展</div>
<div class="ws-idle-hint">共 38 个内置工具 · MCP 动态扩展</div>
</div>
`;
return;
@@ -859,7 +859,7 @@ function getToolDisplayName(name: string): string {
const names: Record<string, string> = {
read_file: '读取文件', write_file: '写入文件', list_directory: '列出目录',
search_files: '搜索文件', create_directory: '创建目录', delete_file: '删除文件', run_command: '执行命令',
move_file: '移动文件', copy_file: '复制文件', web_fetch: '网页抓取', append_file: '追加文件',
move_file: '移动文件', copy_file: '复制文件', web_fetch: '网页抓取',
edit_file: '编辑文件', get_file_info: '文件信息', tree: '目录树', download_file: '下载文件',
diff_files: '文件对比', replace_in_files: '批量替换', read_multiple_files: '批量读取',
git: 'Git 操作', compress: '压缩/解压', web_search: '联网搜索',
@@ -876,7 +876,7 @@ function getToolIcon(name: string): string {
const icons: Record<string, string> = {
read_file: '📄', write_file: '✏️', list_directory: '📁',
search_files: '🔍', create_directory: '📂', delete_file: '🗑️', run_command: '💻',
move_file: '📦', copy_file: '📋', web_fetch: '🌐', append_file: '',
move_file: '📦', copy_file: '📋', web_fetch: '🌐',
edit_file: '✂️', get_file_info: '️', tree: '🌳', download_file: '⬇️',
diff_files: '🔀', replace_in_files: '🔄', read_multiple_files: '📚',
git: '🔖', compress: '🗜️', web_search: '🔍',
+2 -6
View File
@@ -415,7 +415,7 @@
<div class="modal-body">
<div class="help-section"><h4>🚀 快速开始</h4><ol><li>确保 Ollama 已启动(默认 <code>http://127.0.0.1:11434</code>,可在设置中修改)</li><li>顶部模型栏选择一个模型,右侧会显示模型能力徽章(🧠 Think / 👁️ Vision / 🔧 Tools</li><li>输入消息,按 <kbd>Enter</kbd> 发送,<kbd>Shift+Enter</kbd> 换行</li></ol></div>
<div class="help-section"><h4>💬 聊天功能</h4><ul><li><strong>流式回复</strong> — 实时打字效果,随时点 ■ 停止</li><li><strong>Think 推理</strong> — 下方 Think 按钮切换,让模型展示深度思考过程(需模型支持,如 Qwen3)</li><li><strong>上下文长度自动检测</strong> — 切换模型时自动从模型元数据获取实际支持的上下文长度(如 Qwen3.6 27B = 262,144 tokens),无需手动配置</li><li><strong>多模态</strong> — 上传图片,模型需支持 Vision 能力。图片自动压缩至合适分辨率,节省上下文</li><li><strong>文件分析</strong> — 支持 50+ 种文本/代码格式,单文件 ≤500KB,自动剥离注释并按上下文预算智能截断</li><li><strong>AI 回复顶部</strong> — 每条 AI 回复上方展示 📋 系统提示词折叠卡片,可点击查看实际发送给模型的完整上下文</li></ul></div>
<div class="help-section"><h4>🔧 Tool Calling(始终开启)</h4><ul><li>所有消息均通过 <strong>Agent Loop 自主调用本地工具</strong>,像一个本地 Agent,无普通聊天模式</li><li><strong>39 个工具</strong>,分为 9 类:<ul><li><strong>文件系统</strong>17 个):read_file / write_file / list_directory / search_files / create_directory / delete_file / move_file / copy_file / append_file / edit_file / get_file_info / tree / download_file / diff_files / replace_in_files / read_multiple_files / compress</li><li><strong>命令执行</strong>1 个):run_command(实时流式输出,支持自动/需确认/禁用三种模式,可配置超时)</li><li><strong>联网搜索</strong>2 个):web_search(4引擎并行+智能排序+时间过滤+摘要增强)/ web_fetch(反爬headers+UA自动切换+自动回退浏览器渲染)</li><li><strong>Git</strong>(1 个):git17 个子操作,push/pull/clone 内置60-120s超时保护)</li><li><strong>浏览器控制</strong>9 个):browser_open / browser_screenshot / browser_evaluate / browser_extract / browser_click / browser_type / browser_scroll / browser_wait / browser_close</li><li><strong>记忆 & 会话 & Skill</strong>8 个):memory_search / memory_add / memory_replace / memory_remove / session_list / session_read / skill_list / skill_view</li><li><strong>子代理</strong>1 个):spawn_task</li></ul></li><li>read_file 支持 binary模式读取/base64解码/字节分页/2000行默认+截断hint自动续读</li><li>write_file 支持 base64写二进制文件/mode(overwrite+append)合并append_file功能/10MB</li><li>edit_file 支持 use_regex 正则替换</li><li>search_files 支持正则表达式搜索(use_regex=true</li><li>browser_screenshot 支持全页面截图+元素截图</li><li>browser_extract 支持CSS选择器提取特定区域</li><li>new browser_wait 等待元素出现或定时等待</li><li><code>run_command</code> 支持三模式切换:自动/需确认/禁用</li><li>危险命令(<code>rm -rf</code><code>mkfs</code>、反弹 shell 等)和系统路径(<code>/etc</code><code>~/.ssh</code> 等)被自动拦截</li><li>工具调用以<strong>可视化卡片</strong>展示状态(pending → running → success/error),在工作空间🔧工具页签中显示</li><li>默认最大 <strong>85 轮</strong>工具调用循环,上下文使用率>80%时自动缩减到3轮</li><li>独立工具自动<strong>并行执行</strong>(16个只读工具加入并行白名单),有依赖关系的工具串行执行</li></ul></div>
<div class="help-section"><h4>🔧 Tool Calling(始终开启)</h4><ul><li>所有消息均通过 <strong>Agent Loop 自主调用本地工具</strong>,像一个本地 Agent,无普通聊天模式</li><li><strong>38 个工具</strong>,分为 9 类:<ul><li><strong>文件系统</strong>16 个):read_file / write_file / list_directory / search_files / create_directory / delete_file / move_file / copy_file / edit_file / get_file_info / tree / download_file / diff_files / replace_in_files / read_multiple_files / compress</li><li><strong>命令执行</strong>1 个):run_command(实时流式输出,支持自动/需确认/禁用三种模式,可配置超时)</li><li><strong>联网搜索</strong>2 个):web_search(4引擎并行+智能排序+时间过滤+摘要增强)/ web_fetch(反爬headers+UA自动切换+自动回退浏览器渲染)</li><li><strong>Git</strong>(1 个):git17 个子操作,push/pull/clone 内置60-120s超时保护)</li><li><strong>浏览器控制</strong>9 个):browser_open / browser_screenshot / browser_evaluate / browser_extract / browser_click / browser_type / browser_scroll / browser_wait / browser_close</li><li><strong>记忆 & 会话 & Skill</strong>8 个):memory_search / memory_add / memory_replace / memory_remove / session_list / session_read / skill_list / skill_view</li><li><strong>子代理</strong>1 个):spawn_task</li></ul></li><li>read_file 支持 binary模式读取/base64解码/字节分页/2000行默认+截断hint自动续读</li><li>write_file 支持 base64写二进制文件/mode(overwrite+append)合并append_file功能/10MB</li><li>edit_file 支持 use_regex 正则替换</li><li>search_files 支持正则表达式搜索(use_regex=true</li><li>browser_screenshot 支持全页面截图+元素截图</li><li>browser_extract 支持CSS选择器提取特定区域</li><li>new browser_wait 等待元素出现或定时等待</li><li><code>run_command</code> 支持三模式切换:自动/需确认/禁用</li><li>危险命令(<code>rm -rf</code><code>mkfs</code>、反弹 shell 等)和系统路径(<code>/etc</code><code>~/.ssh</code> 等)被自动拦截</li><li>工具调用以<strong>可视化卡片</strong>展示状态(pending → running → success/error),在工作空间🔧工具页签中显示</li><li>默认最大 <strong>85 轮</strong>工具调用循环,上下文使用率>80%时自动缩减到3轮</li><li>独立工具自动<strong>并行执行</strong>(16个只读工具加入并行白名单),有依赖关系的工具串行执行</li></ul></div>
<div class="help-section"><h4>🧠 Agent 记忆系统</h4><ul><li>设置中开启后,AI 从对话中<strong>自动提取关键信息</strong>(事实/偏好/规则),跨会话持续积累</li><li><strong>增量提取</strong>:长对话中每 20 轮自动触发轻量级记忆提取,不等到对话结束</li><li>对话结束时自动触发完整记忆提取</li><li>新对话时自动检索相关记忆注入上下文,让 AI "记住"你</li><li>点击顶部 🧠 按钮打开记忆面板:搜索、筛选、编辑、删除</li><li><strong>记忆容量上限 500 条</strong>,超限时自动清理低价值条目(规则类型受保护)</li><li><strong>向量语义搜索</strong>:在设置中选择嵌入模型后,支持语义级别记忆检索(更精准)</li><li>未选择嵌入模型时,使用关键词匹配检索记忆</li><li>AI 可通过 memory 工具主动管理记忆</li><li>记忆写入前自动安全扫描(prompt injection / 敏感信息 / 不可见字符检测)</li><li>记忆存储在本地 SQLite,不会上传到任何服务器</li></ul></div>
<div class="help-section"><h4>🤖 Agent Loop v0.10.3 增强</h4><ul><li><strong>智能上下文压缩</strong>:滑动窗口 + LLM结构化JSON摘要,超过50%上下文窗口自动触发</li><li><strong>流式调用超时保护</strong>:可配置超时(默认300s),Ollama 卡死不再永久阻塞</li><li><strong>HTTP/MCP 超时可配</strong>:设置面板可分别调整 HTTP(默认30s)和 MCP(默认60s)超时</li><li><strong>Final Answer 智能检测</strong>:多模式匹配(Final Answer/最终答案/最终回答/总结),避免漏检或误触</li><li><strong>Token 感知迭代预算</strong>:上下文使用率>80%时自动缩减剩余轮次到3轮</li><li><strong>工具缓存 TTL</strong>:搜索5分钟/网页10分钟/文件30分钟,过期自动刷新</li><li><strong>自动子任务拆解</strong>:检测"同时/分别/以及"等并行关键词,自动 spawn 子代理并行处理</li><li><strong>旧工具结果自动截断</strong>:超过10轮的工具结果自动截断到500字符,控制上下文膨胀</li></ul></div>
<div class="help-section"><h4>🔌 MCPModel Context Protocol</h4><ul><li>支持连接外部 MCP Server,动态扩展工具能力</li><li>设置面板可添加/启用/禁用/删除 MCP 服务器</li><li>MCP 工具以 <code>mcp_{server}_{tool}</code> 前缀注册,与内置工具统一调度</li><li>启动时自动连接已启用的 MCP 服务器</li></ul></div>
@@ -433,7 +433,7 @@
<div class="modal-overlay" id="toolsModal" style="display:none;">
<div class="modal">
<div class="modal-header">
<h3>🔧 工具面板(39 个)</h3>
<h3>🔧 工具面板(38 个)</h3>
<button class="icon-btn" id="btnCloseTools">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
@@ -479,10 +479,6 @@
<div class="tool-card-header"><span class="tool-card-icon">🌐</span><span class="tool-card-name">web_fetch</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">抓取网页内容,反爬headers+UA切换+自动回退浏览器渲染</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon"></span><span class="tool-card-name">append_file</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">追加内容到文件末尾</div>
</div>
<div class="tool-card">
<div class="tool-card-header"><span class="tool-card-icon">✂️</span><span class="tool-card-name">edit_file</span><span class="tool-card-badge auto">自动</span></div>
<div class="tool-card-desc">查找替换(支持正则use_regex),全部或单次替换</div>
+1 -1
View File
@@ -6,7 +6,7 @@
- **名称**: Metona
- **角色**: Metona Ollama 桌面 AI 助手
- **核心能力**: ReAct Agent Loop v0.10.3 + 39 个增强工具 + 记忆系统 + 技能自动生成 + 智能上下文压缩
- **核心能力**: ReAct Agent Loop v0.10.3 + 38 个增强工具 + 记忆系统 + 技能自动生成 + 智能上下文压缩
## 性格与语气
+2 -2
View File
@@ -66,7 +66,7 @@ const TOOL_MAX_RESULT_SIZE: Record<string, number> = {
};
/** v4.1: 工具并行执行 — 依赖检测用 */
const TOOLS_WITH_DATA_DEPS = new Set(['web_fetch', 'edit_file', 'append_file', 'move_file', 'delete_file']);
const TOOLS_WITH_DATA_DEPS = new Set(['web_fetch', 'edit_file', 'move_file', 'delete_file']);
/** 始终可并行的只读/独立工具(无数据依赖,永远可以同批执行) */
const ALWAYS_PARALLEL = new Set([
@@ -80,7 +80,7 @@ const ALWAYS_PARALLEL = new Set([
const VALID_TOOL_NAMES = new Set([
'read_file', 'write_file', 'list_directory', 'search_files', 'create_directory',
'delete_file', 'run_command', 'move_file', 'copy_file', 'web_fetch', 'web_search',
'append_file', 'edit_file', 'get_file_info', 'tree', 'download_file', 'diff_files',
'edit_file', 'get_file_info', 'tree', 'download_file', 'diff_files',
'replace_in_files', 'read_multiple_files', 'git', 'compress',
'memory_search', 'memory_add', 'memory_replace', 'memory_remove', 'session_list', 'session_read',
'skill_list', 'skill_view'
+3 -19
View File
@@ -177,22 +177,6 @@ export const TOOL_DEFINITIONS: ToolDefinition[] = [
}
}
},
{
type: 'function',
function: {
name: 'append_file',
description: 'Append content to the end of a file. Creates the file if it doesn\'t exist. Requires user confirmation.',
parameters: {
type: 'object',
required: ['path', 'content'],
properties: {
path: { type: 'string', description: 'File path to append to.' },
content: { type: 'string', description: 'Content to append.' },
newline: { type: 'boolean', description: 'Add a newline before appending. Default: true.' }
}
}
}
},
{
type: 'function',
function: {
@@ -662,7 +646,7 @@ let enabledTools: Set<string> = new Set([
'read_file', 'list_directory', 'search_files',
'write_file', 'create_directory', 'delete_file',
'run_command',
'move_file', 'copy_file', 'web_fetch', 'web_search', 'append_file', 'edit_file',
'move_file', 'copy_file', 'web_fetch', 'web_search', 'edit_file',
'get_file_info', 'tree', 'download_file', 'diff_files', 'replace_in_files',
'read_multiple_files', 'git', 'compress',
'memory_search', 'memory_add', 'memory_replace', 'memory_remove',
@@ -882,7 +866,7 @@ export function getToolIcon(name: string): string {
const icons: Record<string, string> = {
read_file: '📄', write_file: '✏️', list_directory: '📁',
search_files: '🔍', create_directory: '📂', delete_file: '🗑️', run_command: '💻',
move_file: '📦', copy_file: '📋', web_fetch: '🌐', append_file: '',
move_file: '📦', copy_file: '📋', web_fetch: '🌐',
edit_file: '✂️', get_file_info: '️', tree: '🌳', download_file: '⬇️',
diff_files: '🔀', replace_in_files: '🔄', read_multiple_files: '📚',
git: '🔖', compress: '🗜️', web_search: '🔍',
@@ -899,7 +883,7 @@ export function formatToolName(name: string): string {
read_file: '读取文件', write_file: '写入文件', list_directory: '列出目录',
search_files: '搜索文件', create_directory: '创建目录', delete_file: '删除文件',
run_command: '执行命令', move_file: '移动文件', copy_file: '复制文件',
web_fetch: '网页抓取', append_file: '追加文件', edit_file: '编辑文件',
web_fetch: '网页抓取', edit_file: '编辑文件',
get_file_info: '文件信息', tree: '目录树', download_file: '下载文件',
diff_files: '文件对比', replace_in_files: '批量替换', read_multiple_files: '批量读取',
git: 'Git 操作', compress: '压缩/解压', web_search: '联网搜索',