diff --git a/README.md b/README.md index 62b0e0a..ba0a31a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@

- version + version electron typescript license @@ -35,10 +35,9 @@ | | 功能 | 说明 | |:---:|:---|:---| | 🤖 | **ReAct Agent Loop** | 始终开启的唯一对话模式。Thought → Action → Observation → Reflection 完整循环,最大 85 轮(可配置),自动重试 2 次,工具去重,并行/链式执行 | -| 🔧 | **44 个内置工具** | 文件系统(16个) · 命令执行 · 联网搜索 · 浏览器控制(9个) · Git · 记忆 · 会话 · 子代理 · 系统工具 | +| 🔧 | **42 个内置工具** | 文件系统(16个) · 命令执行 · 联网搜索 · 浏览器控制(9个) · Git · 记忆 · 会话 · 子代理 · 系统工具 | | 🧠 | **智能记忆系统** | 三类记忆(fact / preference / rule),FTS5 全文搜索 + 向量语义搜索,写入前安全扫描,容量 500 条,90 天衰减 | | 📋 | **自定义文件** | SOUL.md(人格,不可压缩)+ AGENT.md(行为准则)+ USER.md(用户画像),工作空间优先,内置默认 fallback | -| 🎯 | **技能自动生成 v1.1** | 工具调用链自动提取 → 语义匹配 → 参数自优化 → 未使用衰减 → 技能链合并,越用越精准 | | 🌐 | **MCP 协议扩展** | JSON-RPC 2.0 over stdio,动态工具发现,Shadowing 防护 | | 🔍 | **联网搜索(双模式)** | SearXNG 元搜索引擎 JSON API(70+引擎聚合)/ 四引擎 HTML 解析(Bing+百度+搜狗+360),双模式可切换;web_fetch 支持反爬+UA切换+浏览器回退 | | 🌏 | **浏览器控制** | 打开网页 · 截图 · 执行 JS · 提取内容 · 点击 · 输入 · 滚动 · 关闭 | @@ -138,12 +137,10 @@

-📊 技能 / 会话 / 子代理(5 个) +📊 会话 / 子代理(3 个) | 工具 | 功能 | |------|------| -| `skill_list` | 列出已生成的技能 | -| `skill_view` | 查看技能详情 | | `session_list` | 列出历史会话 | | `session_read` | 读取会话内容 | | `spawn_task` | 委派子代理(独立上下文 + 超时保护) | @@ -173,11 +170,9 @@ ↓ 记忆检索 (FTS5 + 向量语义搜索) → 上下文注入 ↓ - 技能匹配 (语义匹配 + 参数自优化 + 衰减 + 链合并) - ↓ Ollama API (流式响应,num_ctx 自动检测) ↓ - Tool Registry (44 内置 + MCP 动态) + Tool Registry (42 内置 + MCP 动态) ↓ 观察结果 → 反思 → 循环 / 最终回答 ``` @@ -204,7 +199,7 @@ main.ts (入口) ### 🗄️ 数据库 -SQLite (sql.js WASM),7 张表,WAL 模式 + FTS5 全文搜索: +SQLite (sql.js WASM),6 张表,WAL 模式 + FTS5 全文搜索: | 表 | 用途 | |---|---| @@ -214,7 +209,6 @@ SQLite (sql.js WASM),7 张表,WAL 模式 + FTS5 全文搜索: | `memories` | Agent 记忆(FTS5 + 向量嵌入) | | `settings` | 设置(JSON 序列化) | | `traces` | ReAct 执行轨迹 | -| `skills` | 自动生成的技能 | ## 🔒 安全机制 @@ -245,7 +239,7 @@ npm start ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist ``` -产出:`release/Metona Ollama Setup v0.11.10.exe` +产出:`release/Metona Ollama Setup v0.11.11.exe` ## 🛠️ 常用命令 @@ -276,10 +270,9 @@ npm run dist # 构建 Windows 安装包 | | Feature | Description | |:---:|:---|:---| | 🤖 | **ReAct Agent Loop** | Always-on, only chat mode. Full Thought → Action → Observation → Reflection cycle, up to 85 iterations (configurable), auto-retry 2 times, tool dedup, parallel/chain execution | -| 🔧 | **44 Built-in Tools** | File system · Command · Web search · Browser · Git · Memory · Skills · Sessions · Sub-agent · System | +| 🔧 | **42 Built-in Tools** | File system · Command · Web search · Browser · Git · Memory · Sessions · Sub-agent · System | | 🧠 | **Smart Memory System** | Three types (fact / preference / rule), FTS5 + vector semantic search, pre-write security scan, 500 capacity, 90-day decay | | 📋 | **Custom Files** | SOUL.md (persona, never compressed) + AGENT.md (behavior rules) + USER.md (user profile), workspace-first with built-in defaults | -| 🎯 | **Auto Skill Generation v1.1** | Tool chain extraction → semantic matching → parameter self-optimization → decay → chain merging | | 🌐 | **MCP Protocol Extension** | JSON-RPC 2.0 over stdio, dynamic tool discovery, Shadowing protection | | 🔍 | **Web Search (dual-mode)** | SearXNG meta-search JSON API (70+ engines) / quad-engine HTML parsing (Bing+Baidu+Sogou+360), switchable; web_fetch with auto-retry+mobile UA+SPA browser fallback | | 🌏 | **Browser Control** | Open pages · Screenshot · JS execution · Content extraction · Click · Type · Scroll · Close | @@ -378,12 +371,10 @@ npm run dist # 构建 Windows 安装包
-📊 Skills / Sessions / Sub-agent (5) +📊 Sessions / Sub-agent (3) | Tool | Function | |------|------| -| `skill_list` | List generated skills | -| `skill_view` | View skill details | | `session_list` | List session history | | `session_read` | Read session content | | `spawn_task` | Delegate sub-agent (isolated context + timeout) | @@ -413,7 +404,7 @@ User message → workspace SOUL.md (never compressed) → AGENT.md → USER.md ↓ Ollama API (Streaming Response) ↓ - Tool Registry (44 Built-in + MCP Dynamic) + Tool Registry (42 Built-in + MCP Dynamic) ↓ Observation → Reflection → Loop / Final Answer ``` @@ -440,7 +431,7 @@ main.ts (Entry) ### 🗄️ Database -SQLite (sql.js WASM), 7 tables, WAL mode + FTS5 full-text search: +SQLite (sql.js WASM), 6 tables, WAL mode + FTS5 full-text search: | Table | Purpose | |---|---| @@ -450,7 +441,6 @@ SQLite (sql.js WASM), 7 tables, WAL mode + FTS5 full-text search: | `memories` | Agent memories (FTS5 + vector embedding) | | `settings` | Settings (JSON serialization) | | `traces` | ReAct execution traces | -| `skills` | Auto-generated skills | ## 🔒 Security @@ -481,7 +471,7 @@ npm start ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist ``` -Output: `release/Metona Ollama Setup v0.11.10.exe` +Output: `release/Metona Ollama Setup v0.11.11.exe` ## 🛠️ Common Commands diff --git a/docs/BUILD.md b/docs/BUILD.md index 7339b98..cfbba20 100644 --- a/docs/BUILD.md +++ b/docs/BUILD.md @@ -91,7 +91,7 @@ npm run dist # NSIS 安装包 | 文件 | 说明 | |------|------| -| `release/Metona Ollama Setup v0.11.10.exe` | NSIS 安装包(可选目录、创建快捷方式) | +| `release/Metona Ollama Setup v0.11.11.exe` | NSIS 安装包(可选目录、创建快捷方式) | > 仅提供 NSIS 安装包。 @@ -103,7 +103,7 @@ npm run dist # NSIS 安装包 TOKEN="your_access_token" curl -X POST "https://gitee.com/api/v5/repos/thzxx/metona-ollama-desktop/releases?access_token=$TOKEN" \ -H "Content-Type: application/json" \ - -d '{"tag_name":"v0.11.10","name":"v0.11.10","body":"Release notes","target_commitish":"master"}' + -d '{"tag_name":"v0.11.11","name":"v0.11.11","body":"Release notes","target_commitish":"master"}' ``` ### 上传附件 @@ -112,7 +112,7 @@ curl -X POST "https://gitee.com/api/v5/repos/thzxx/metona-ollama-desktop/release TOKEN="your_access_token" RELEASE_ID="" curl -X POST "https://gitee.com/api/v5/repos/thzxx/metona-ollama-desktop/releases/$RELEASE_ID/attach_files?access_token=$TOKEN" \ - -H "Content-Type: multipart/form-data" -F "file=@release/Metona Ollama Setup v0.11.10.exe" + -H "Content-Type: multipart/form-data" -F "file=@release/Metona Ollama Setup v0.11.11.exe" ``` ## 故障排查 diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 811292b..6fc6075 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -1,6 +1,6 @@ # Metona Ollama Desktop — 开发规范 -> 版本: v0.11.10 | 更新: 2026-06-05 | 维护: 项目团队 +> 版本: v0.11.11 | 更新: 2026-06-05 | 维护: 项目团队 --- @@ -43,7 +43,7 @@ src/ │ ├── tray.ts # 系统托盘 │ ├── utils.ts # 工具函数 │ └── db/ -│ └── sqlite.ts # SQLite 数据库层(7 张表 + FTS5) +│ └── sqlite.ts # SQLite 数据库层(6 张表 + FTS5) ├── renderer/ # 渲染进程 │ ├── main.ts # 入口、全局初始化 │ ├── types.d.ts # 完整类型定义 @@ -53,12 +53,11 @@ src/ │ ├── components/ # 14 个 UI 组件 │ ├── services/ │ │ ├── agent-engine.ts # ReAct Agent Loop 引擎 -│ │ ├── tool-registry.ts # 工具注册与调度(39 个内置工具 + MCP 动态) +│ │ ├── tool-registry.ts # 工具注册与调度(42 个内置工具 + MCP 动态) │ │ ├── memory-manager.ts # 记忆管理核心 │ │ ├── vector-memory.ts # 记忆向量索引(IVF) │ │ ├── vector-store.ts # 向量存储 + IVF 索引 │ │ ├── context-manager.ts # 上下文窗口管理 -│ │ ├── skill-manager.ts # 技能自动生成(Level 0) │ │ ├── sub-agent.ts # 子代理委派 │ │ ├── mcp-client.ts # MCP 渲染端客户端 │ │ ├── log-service.ts # 结构化日志 @@ -155,11 +154,11 @@ import { ### 5.1 五大子系统 ``` -① Agent 系统 → agent-engine.ts + tool-registry.ts(39 内置工具 + MCP 动态) +① Agent 系统 → agent-engine.ts + tool-registry.ts(42 内置工具 + MCP 动态) ② 记忆系统 → memory-manager.ts + vector-memory.ts ③ 向量存储 → vector-store.ts(IVF 索引) ④ 工作空间 → workspace.ts (主进程) + workspace-panel.ts (渲染进程) -⑤ 数据层 → db/sqlite.ts(SQLite, 7 张表, FTS5) +⑤ 数据层 → db/sqlite.ts(SQLite, 6 张表, FTS5) ``` ### 5.2 ReAct Agent Loop @@ -177,7 +176,7 @@ import { ### 5.3 SQLite 数据库 -7 张表,WAL 模式 + NORMAL 同步: +6 张表,WAL 模式 + NORMAL 同步: | 表 | 用途 | 关键特性 | |---|---|---| @@ -187,7 +186,6 @@ import { | `memories` | Agent 记忆 | FTS5 全文搜索,向量嵌入,容量 500 上限 | | `settings` | 设置 | JSON 序列化 | | `traces` | ReAct 执行轨迹 | Agent 可观测性 | -| `skills` | 自动生成的可复用技能 | 成功/失败次数、平均时长 | ### 5.4 联网搜索联动 diff --git a/package-lock.json b/package-lock.json index 3966187..02235d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "metona-ollama-desktop", - "version": "0.11.10", + "version": "0.11.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "metona-ollama-desktop", - "version": "0.11.10", + "version": "0.11.11", "license": "MIT", "dependencies": { "ffmpeg-static": "^5.2.0", diff --git a/package.json b/package.json index fdb2fbf..a5448b2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "metona-ollama-desktop", - "version": "0.11.10", - "description": "Metona Ollama - TypeScript + Electron 桌面 AI 聊天客户端", + "version": "0.11.11", + "description": "Metona Ollama - TypeScript + Electron \u684c\u9762 AI \u804a\u5929\u5ba2\u6237\u7aef", "main": "dist/main/main.js", "author": "thzxx", "license": "MIT", diff --git a/release-notes.md b/release-notes.md index 39b79ec..871c298 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,7 +4,7 @@ ## 项目简介 -Metona Ollama Desktop 是一个基于 **Ollama** 的本地 AI 桌面客户端,采用 TypeScript + Electron 构建。所有 AI 推理均在本地完成,数据不离开本机。支持 ReAct Agent Loop、44 个内置工具、智能记忆系统、MCP 协议扩展、四引擎联网搜索、浏览器控制等能力。 +Metona Ollama Desktop 是一个基于 **Ollama** 的本地 AI 桌面客户端,采用 TypeScript + Electron 构建。所有 AI 推理均在本地完成,数据不离开本机。支持 ReAct Agent Loop、42 个内置工具、智能记忆系统、MCP 协议扩展、四引擎联网搜索、浏览器控制等能力。 --- @@ -18,7 +18,7 @@ Metona Ollama Desktop 是一个基于 **Ollama** 的本地 AI 桌面客户端, - 链式工具调用:web_search → web_fetch、list_directory → read_file 等模式 - 文本解析兜底:当模型未通过 tool_calls 返回时,从文本中提取 Action/Input -### 🔧 44 个内置工具 +### 🔧 42 个内置工具 | 分类 | 数量 | 工具 | |------|------|------| @@ -28,7 +28,6 @@ Metona Ollama Desktop 是一个基于 **Ollama** 的本地 AI 桌面客户端, | 浏览器控制 | 8 | browser_open, browser_screenshot, browser_evaluate, browser_extract, browser_click, browser_type, browser_scroll, browser_close | | Git | 1 | git(init/clone/add/commit/push/pull/diff/log/status/branch/checkout/merge/stash/reset/tag/remote) | | 记忆管理 | 4 | memory_search, memory_add, memory_replace, memory_remove | -| 技能系统 | 2 | skill_list, skill_view(渐进式加载) | | 会话管理 | 2 | session_list, session_read | | 子代理 | 1 | spawn_task(独立上下文 + 超时保护) | | 系统工具 | 6 | datetime / calculator / random / uuid / json_format / hash | @@ -62,11 +61,6 @@ Metona Ollama Desktop 是一个基于 **Ollama** 的本地 AI 桌面客户端, - Token 消耗趋势图(输入/输出分色显示) - 2 秒自动刷新 -### 📊 技能自动生成系统 -- 从成功的工具调用链中自动提取可复用技能 -- Level 0 简短摘要(~100 tokens),渐进式加载 -- 技能匹配与上下文注入 - ### 🔒 安全机制 - 文件路径安全检查(checkPathAllowed) - 命令执行安全检查(checkCommandAllowed) @@ -91,7 +85,7 @@ Metona Ollama Desktop 是一个基于 **Ollama** 的本地 AI 桌面客户端, ``` ### 数据层 -- **SQLite (sql.js WASM)**:7 张表(sessions, messages, tool_calls, memories, settings, traces, skills) +- **SQLite (sql.js WASM)**:6 张表(sessions, messages, tool_calls, memories, settings, traces) - **FTS5 全文搜索**:memories 表支持全文检索 - **WAL 模式 + NORMAL 同步**:零原生依赖,无需 electron-rebuild @@ -116,7 +110,6 @@ Metona Ollama Desktop 是一个基于 **Ollama** 的本地 AI 桌面客户端, ## 最近改进 - **`/compress` 上下文压缩**:支持手动触发 LLM 摘要压缩,长对话不再丢失上下文 -- **Skill 渐进式加载**:技能摘要(Level 0)仅 ~100 tokens,按需加载完整工具链 - **Token 全局统计**:仪表盘新增跨会话累计 Token 分析视图 - **图表重写**:Token 消耗趋势图重写 — 网格线 + 正确高度计算 + 柱子底部对齐 - **Agent Loop 稳定性**:修复 abortController 在自动压缩前未定义的严重 bug diff --git a/src/main/db/sqlite.ts b/src/main/db/sqlite.ts index 06c69f9..80a80d2 100644 --- a/src/main/db/sqlite.ts +++ b/src/main/db/sqlite.ts @@ -205,22 +205,6 @@ export async function initDatabase(): Promise { ); CREATE INDEX IF NOT EXISTS idx_traces_session ON traces(session_id, created_at); - -- v4.2 技能表(自动从成功工具调用链提取) - CREATE TABLE IF NOT EXISTS skills ( - id TEXT PRIMARY KEY, - name TEXT NOT NULL, - description TEXT NOT NULL, - summary TEXT DEFAULT '', - trigger_keywords TEXT, - tool_chain TEXT NOT NULL, - success_count INTEGER DEFAULT 0, - fail_count INTEGER DEFAULT 0, - avg_duration_ms INTEGER DEFAULT 0, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL, - last_used_at INTEGER - ); - CREATE INDEX IF NOT EXISTS idx_skills_updated ON skills(updated_at DESC); `); // 兼容迁移:为已有 messages 表补充 attachments 列(文件/视频等附件 JSON) @@ -228,8 +212,6 @@ export async function initDatabase(): Promise { // 兼容迁移:为已有 messages 表补充 prompt_eval_count 列 - // 兼容迁移:为已有 skills 表补充 summary 列 - try { db.run("ALTER TABLE skills ADD COLUMN summary TEXT DEFAULT ''"); } catch { /* 列已存在,忽略 */ } // 尝试创建 FTS5 全文搜索(可选,sql.js 默认 WASM 可能不包含 FTS5) try { @@ -314,22 +296,6 @@ export interface ToolCallRow { created_at: number; } -// v4.2 -export interface SkillRow { - id: string; - name: string; - description: string; - summary: string; - trigger_keywords: string | null; - tool_chain: string; - success_count: number; - fail_count: number; - avg_duration_ms: number; - created_at: number; - updated_at: number; - last_used_at: number | null; -} - export interface TraceRow { id: string; session_id: string; @@ -527,62 +493,6 @@ export function getTracesBySession(sessionId: string): TraceRow[] { return queryAll(getDb(), 'SELECT * FROM traces WHERE session_id = ? ORDER BY step_index ASC', [sessionId]) as unknown as TraceRow[]; } -// ─── Skills CRUD (v4.2) ─── - -export function saveSkill(skill: SkillRow): string { - runExec(getDb(), `INSERT OR REPLACE INTO skills (id, name, description, summary, trigger_keywords, tool_chain, success_count, fail_count, avg_duration_ms, created_at, updated_at, last_used_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - [skill.id, skill.name, skill.description, skill.summary || '', skill.trigger_keywords, skill.tool_chain, - skill.success_count, skill.fail_count, skill.avg_duration_ms, - skill.created_at, skill.updated_at, skill.last_used_at] - ); - persist(); - return skill.id; -} - - -export function getAllSkills(): SkillRow[] { - return queryAll(getDb(), 'SELECT * FROM skills ORDER BY success_count DESC, updated_at DESC') as unknown as SkillRow[]; -} - -export function deleteSkill(id: string): void { - runExec(getDb(), 'DELETE FROM skills WHERE id = ?', [id]); - persist(); -} - -export function clearAllSkills(): void { - getDb().run('DELETE FROM skills'); - persist(); -} - -export function searchSkills(keywords: string, limit = 5): SkillRow[] { - const likeQ = `%${keywords.replace(/[^\w\u4e00-\u9fff]/g, '%')}%`; - return queryAll(getDb(), ` - SELECT * FROM skills - WHERE trigger_keywords LIKE ? OR name LIKE ? OR description LIKE ? - ORDER BY success_count DESC, updated_at DESC - LIMIT ? - `, [likeQ, likeQ, likeQ, limit]) as unknown as SkillRow[]; -} - -export function incrementSkillUsage(id: string, success: boolean, durationMs: number): void { - const d = getDb(); - const skill = queryOne(d, 'SELECT success_count, fail_count, avg_duration_ms FROM skills WHERE id = ?', [id]) as any; - if (!skill) return; - - const newSuccess = success ? skill.success_count + 1 : skill.success_count; - const newFail = success ? skill.fail_count : skill.fail_count + 1; - const totalUses = newSuccess + newFail; - // 指数移动平均 - const alpha = 0.3; - const newAvg = Math.round(alpha * durationMs + (1 - alpha) * (skill.avg_duration_ms || durationMs)); - - runExec(d, `UPDATE skills SET success_count = ?, fail_count = ?, avg_duration_ms = ?, updated_at = ?, last_used_at = ? WHERE id = ?`, - [newSuccess, newFail, newAvg, Date.now(), Date.now(), id] - ); - persist(); -} - // ─── Token 全局统计 ─── export interface SessionTokenStat { diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 68f4fbe..a758531 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -15,7 +15,6 @@ import { saveSetting, getSetting, saveToolCall, getToolCallsBySession, saveTrace, getTracesBySession, - saveSkill, getAllSkills, deleteSkill, clearAllSkills, searchSkills, incrementSkillUsage, exportAllSessions, importSessions, getAllSessionsTokenStats } from './db/sqlite.js'; @@ -433,32 +432,6 @@ export async function setupIPC(): Promise { catch (err) { sendLog('error', '导入失败', (err as Error).message); return { imported: 0, skipped: 0 }; } }); - // ── Skills (v4.2) ── - ipcMain.handle('db:saveSkill', (_, skill) => { - try { return { success: true, id: saveSkill(skill) }; } - catch (err) { return { success: false, error: (err as Error).message }; } - }); - ipcMain.handle('db:getAllSkills', () => { - try { return getAllSkills(); } - catch { return []; } - }); - ipcMain.handle('db:deleteSkill', (_, id) => { - try { deleteSkill(id); return { success: true }; } - catch (err) { return { success: false, error: (err as Error).message }; } - }); - ipcMain.handle('db:clearAllSkills', () => { - try { clearAllSkills(); return { success: true }; } - catch (err) { return { success: false, error: (err as Error).message }; } - }); - ipcMain.handle('db:searchSkills', (_, query: string, limit?: number) => { - try { return searchSkills(query, limit ?? 5); } - catch { return []; } - }); - ipcMain.handle('db:incrementSkillUsage', (_, id: string, success: boolean, durationMs: number) => { - try { incrementSkillUsage(id, success, durationMs); return { success: true }; } - catch (err) { return { success: false, error: (err as Error).message }; } - }); - // ── Token 全局统计 ── ipcMain.handle('db:getAllTokenStats', () => { try { return getAllSessionsTokenStats(); } diff --git a/src/main/menu.ts b/src/main/menu.ts index a92e323..46838a7 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -101,7 +101,7 @@ export function createMenu(): void { dialog.showMessageBox(mainWindow!, { type: 'info', title: '关于 Metona Ollama', - message: 'Metona Ollama Desktop v0.11.10', + message: 'Metona Ollama Desktop v0.11.11', detail: 'TypeScript + Electron Ollama AI 聊天客户端\n\nhttps://gitee.com/thzxx/metona-ollama', icon: getIconPath() }); diff --git a/src/main/preload.ts b/src/main/preload.ts index 4e5091d..35bb05b 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -77,13 +77,7 @@ contextBridge.exposeInMainWorld('metonaDesktop', { getTraces: (sessionId: string) => ipcRenderer.invoke('db:getTraces', sessionId), exportSessions: () => ipcRenderer.invoke('db:exportSessions'), importSessions: (data: unknown) => ipcRenderer.invoke('db:importSessions', data), - // v4.2 Skills - saveSkill: (skill: unknown) => ipcRenderer.invoke('db:saveSkill', skill), - getAllSkills: () => ipcRenderer.invoke('db:getAllSkills'), - deleteSkill: (id: string) => ipcRenderer.invoke('db:deleteSkill', id), - clearAllSkills: () => ipcRenderer.invoke('db:clearAllSkills'), - searchSkills: (query: string, limit?: number) => ipcRenderer.invoke('db:searchSkills', query, limit), - incrementSkillUsage: (id: string, success: boolean, durationMs: number) => ipcRenderer.invoke('db:incrementSkillUsage', id, success, durationMs), + getAllTokenStats: () => ipcRenderer.invoke('db:getAllTokenStats'), }, workspace: { diff --git a/src/renderer/components/workspace-panel.ts b/src/renderer/components/workspace-panel.ts index ed86ed5..d4ea4c4 100644 --- a/src/renderer/components/workspace-panel.ts +++ b/src/renderer/components/workspace-panel.ts @@ -964,7 +964,7 @@ function getToolDisplayName(name: string): string { diff_files: '文件对比', replace_in_files: '批量替换', read_multiple_files: '批量读取', git: 'Git 操作', compress: '压缩/解压', web_search: '联网搜索', memory_search: '搜索记忆', memory_add: '添加记忆', memory_replace: '替换记忆', memory_remove: '删除记忆', - skill_list: '技能列表', skill_view: '查看技能', + session_list: '会话列表', session_read: '读取会话', spawn_task: '子代理委派', browser_open: '打开网页', browser_screenshot: '浏览器截图', browser_evaluate: '执行JS', browser_extract: '提取内容', browser_click: '点击元素', browser_type: '输入文本', browser_scroll: '滚动页面', browser_close: '关闭浏览器' @@ -981,7 +981,7 @@ function getToolIcon(name: string): string { diff_files: '🔀', replace_in_files: '🔄', read_multiple_files: '📚', git: '🔖', compress: '🗜️', web_search: '🔍', memory_search: '🧠', memory_add: '💾', memory_replace: '✏️', memory_remove: '🗑️', - skill_list: '🎯', skill_view: '👁️', + session_list: '📋', session_read: '📖', spawn_task: '🤖', browser_open: '🌐', browser_screenshot: '📸', browser_evaluate: '⚡', browser_extract: '📰', browser_click: '👆', browser_type: '⌨️', browser_scroll: '↕️', browser_close: '❌' diff --git a/src/renderer/index.html b/src/renderer/index.html index f6be390..c722b4c 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -28,7 +28,7 @@
Metona Ollama - v0.11.10 + v0.11.11
-
-
📚skill_list自动
-
列出所有自动生成的技能(名称、描述、成功率)
-
-
-
📖skill_view自动
-
查看技能详情(完整工具链、参数提示、使用统计)
-
🌐browser_open自动
打开URL,支持等待特定CSS元素出现(SPA页面)
diff --git a/src/renderer/public/AGENT.md b/src/renderer/public/AGENT.md index aec446b..fec2e61 100644 --- a/src/renderer/public/AGENT.md +++ b/src/renderer/public/AGENT.md @@ -13,7 +13,6 @@ - **Git 操作**:完整的版本控制——查看状态、日志、差异,暂存、提交、推送、拉取,分支管理、合并、标签、远程仓库操作 - **记忆管理**:跨会话记住用户的重要信息(事实、偏好、规则),搜索历史记忆,添加/修改/删除记忆条目 - **会话管理**:列出和读取历史会话记录,跨会话引用之前的对话内容 -- **技能系统**:查看从过往成功任务中自动提取的可复用技能,按需加载完整操作链 - **子代理委派**:将独立子任务委派给子代理并行执行,完成后汇总结果 - **系统工具**:获取精确时间、数学计算、随机生成、UUID 生成、JSON 格式化、哈希计算 diff --git a/src/renderer/services/agent-engine.ts b/src/renderer/services/agent-engine.ts index 85ad7d2..eb57941 100644 --- a/src/renderer/services/agent-engine.ts +++ b/src/renderer/services/agent-engine.ts @@ -13,7 +13,6 @@ import { needsConfirmation } from './tool-registry.js'; import { searchMemories, buildMemoryContext, markMemoryUsed, isMemoryEnabled } from './memory-manager.js'; -import { extractSkillsFromToolRecords, matchSkills, buildSkillContext } from './skill-manager.js'; import { showToast } from '../components/toast.js'; import { logInfo, logWarn, logSuccess, logError, logToolStart, logToolResult, logAgentLoop, logModelResponse, logStreamProgress, resetStreamProgress } from './log-service.js'; import { getWorkspaceDirPath } from '../components/workspace-panel.js'; @@ -80,7 +79,7 @@ const TOOLS_WITH_DATA_DEPS = new Set(['web_fetch']); // web_fetch 可能依赖 w const ALWAYS_PARALLEL = new Set([ 'read_file', 'list_directory', 'search_files', 'get_file_info', 'tree', 'web_search', 'browser_screenshot', 'browser_extract', 'browser_evaluate', - 'memory_search', 'session_list', 'session_read', 'skill_list', 'skill_view', + 'memory_search', 'session_list', 'session_read', 'diff_files', 'git', 'datetime', 'calculator', 'random', 'uuid', 'json_format', 'hash', @@ -93,7 +92,6 @@ const VALID_TOOL_NAMES = new Set([ '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', 'datetime', 'calculator', 'random', 'uuid', 'json_format', 'hash' ]); @@ -517,17 +515,6 @@ Shell: ${osInfo.shell} - 如果是 Linux/macOS,使用 Bash 命令(如 ls、cat、grep,路径用 /) - 严禁在 Windows 上执行 Linux 命令,严禁在 Linux 上执行 Windows 命令。`); - // v4.2 注入匹配的技能上下文 - if (useTools && userContent) { - const matchedSkills = await matchSkills(userContent, 3); - if (matchedSkills.length > 0) { - const skillContext = buildSkillContext(matchedSkills); - if (skillContext) { - systemPromptParts.push(skillContext); - logInfo(`技能匹配: ${matchedSkills.length} 个技能已注入`, matchedSkills.map(s => s.name).join(', ')); - } - } - } // 组合 system prompt @@ -915,12 +902,6 @@ Shell: ${osInfo.shell} } catch { /* 不阻塞 */ } } - // v4.2 自动提取技能 - if (allToolRecords.length >= 2) { - try { - await extractSkillsFromToolRecords(allToolRecords, userContent, (currentSession as ChatSession)?.title || ''); - } catch { /* 不阻塞 */ } - } callbacks.onDone(content || '(模型未返回内容)', allToolRecords.length > 0 ? allToolRecords : undefined, makeStats()); return; diff --git a/src/renderer/services/log-service.ts b/src/renderer/services/log-service.ts index f42dbb6..e004521 100644 --- a/src/renderer/services/log-service.ts +++ b/src/renderer/services/log-service.ts @@ -220,7 +220,6 @@ export function logConnection(status: string, detail?: string): void { addLog(st export function logModel(name: string, action: string): void { addLog('info', `模型: ${action}`, name); } export function logSession(action: string, detail?: string): void { addLog('info', `会话: ${action}`, detail); } export function logMemory(action: string, detail?: string): void { addLog('debug', `记忆: ${action}`, detail); } -export function logSkill(action: string, detail?: string): void { addLog('debug', `技能: ${action}`, detail); } export function logRAG(action: string, detail?: string): void { addLog('info', `向量: ${action}`, detail); } export function logSetting(key: string, value: string): void { addLog('debug', `设置: ${key}`, value); } export function logInit(msg: string): void { addLog('info', `初始化: ${msg}`); } diff --git a/src/renderer/services/skill-manager.ts b/src/renderer/services/skill-manager.ts deleted file mode 100644 index a2c56cb..0000000 --- a/src/renderer/services/skill-manager.ts +++ /dev/null @@ -1,480 +0,0 @@ -/** - * SkillManager - 技能自动生成系统 (v4.2) - * 从成功的工具调用链中提取可复用技能,匹配用户意图后注入上下文 - */ - -import { state, KEYS } from '../state/state.js'; -import { generateId } from '../utils/utils.js'; -import { logInfo, logWarn, logSkill } from './log-service.js'; -import type { ToolCallRecord, ChatSession } from '../types.js'; - -/** 技能数据结构 */ -export interface Skill { - id: string; - name: string; - description: string; - /** Level 0 简短摘要(~100 tokens),用于索引展示 */ - summary: string; - trigger_keywords: string | null; - tool_chain: string; // JSON: ToolChainStep[] - success_count: number; - fail_count: number; - avg_duration_ms: number; - created_at: number; - updated_at: number; - last_used_at: number | null; -} - -/** 工具链步骤 */ -interface ToolChainStep { - name: string; - description: string; // 该步骤的作用描述 - args_hint: string; // 参数提示(哪些参数是关键的) -} - -/** 最小工具链长度,太短的不算技能 */ -const MIN_CHAIN_LENGTH = 2; - -/** 技能匹配最低阈值 */ -const MATCH_THRESHOLD = 0.3; - -/** - * 从 Agent Loop 完成后的工具调用记录中提取技能 - * 只提取成功的、链长 >= 2 的工具调用序列 - */ -export async function extractSkillsFromToolRecords( - toolRecords: ToolCallRecord[], - userMessage: string, - sessionTitle: string -): Promise { - const bridge = window.metonaDesktop; - if (!bridge?.db?.saveSkill) return 0; - - // 只看成功的工具调用 - const successfulTools = toolRecords.filter(t => t.status === 'success'); - if (successfulTools.length < MIN_CHAIN_LENGTH) return 0; - - // 提取工具链 - const chain: ToolChainStep[] = successfulTools.map(t => ({ - name: t.name, - description: describeToolStep(t), - args_hint: extractKeyArgs(t) - })); - - const chainNames = chain.map(s => s.name).join(' → '); - - // 检查是否已有完全相同的技能链 - const existingSkills = await bridge.db.getAllSkills() as Skill[]; - const duplicate = existingSkills.find(s => { - try { - const existingChain: ToolChainStep[] = JSON.parse(s.tool_chain); - return existingChain.map((c: ToolChainStep) => c.name).join(' → ') === chainNames; - } catch { return false; } - }); - - if (duplicate) { - // v1.1: 重复链 → 更新参数提示(合并新知)+ 增加计数 - try { - const existingChain: ToolChainStep[] = JSON.parse(duplicate.tool_chain); - let updated = false; - for (let i = 0; i < Math.min(chain.length, existingChain.length); i++) { - const newArgs = chain[i].args_hint.split(',').map(a => a.trim()).filter(Boolean); - const oldArgs = existingChain[i].args_hint.split(',').map(a => a.trim()).filter(Boolean); - const merged = [...new Set([...oldArgs, ...newArgs])]; - const mergedHint = merged.join(', '); - if (mergedHint !== existingChain[i].args_hint) { - existingChain[i].args_hint = mergedHint; - updated = true; - } - } - if (updated) { - duplicate.tool_chain = JSON.stringify(existingChain); - duplicate.success_count++; - duplicate.updated_at = Date.now(); - await bridge.db.saveSkill(duplicate); - logInfo(`技能参数优化: ${duplicate.name}`, `合并了新的参数提示`); - return 1; - } - } catch { /* 更新失败不阻塞 */ } - logInfo(`技能已存在,跳过提取: ${duplicate.name}`); - return 0; - } - - // 从用户消息中提取触发关键词 - const keywords = extractKeywords(userMessage); - - // 生成技能名称和描述 - const name = generateSkillName(chain, userMessage); - const description = generateSkillDescription(chain, sessionTitle); - - const summary = generateSkillSummary(chain, userMessage); - - const skill: Skill = { - id: `skill_${generateId()}`, - name, - description, - summary, - trigger_keywords: keywords, - tool_chain: JSON.stringify(chain), - success_count: 1, - fail_count: 0, - avg_duration_ms: toolRecords.reduce((sum, t) => sum + (t.timestamp - (toolRecords[0]?.timestamp || t.timestamp)), 0), - created_at: Date.now(), - updated_at: Date.now(), - last_used_at: null - }; - - try { - await bridge.db.saveSkill(skill); - logSkill(`新技能提取: ${name}`, `链: ${chainNames}`); - return 1; - } catch (err) { - logWarn('技能保存失败', (err as Error).message); - return 0; - } -} - -/** - * 匹配用户消息与已有技能。 - * v1.1: 关键词 + 语义匹配融合,未使用技能衰减。 - */ -export async function matchSkills(userMessage: string, limit = 3): Promise { - const bridge = window.metonaDesktop; - if (!bridge?.db?.getAllSkills) return []; - - try { - const allSkills = await bridge.db.getAllSkills() as any as Skill[]; - if (allSkills.length === 0) return []; - - const messageLower = userMessage.toLowerCase(); - const messageWords = new Set(messageLower.split(/[\s,,。!?、;:""''()\[\]【】]+/).filter(w => w.length > 1)); - - // ── 语义匹配(如果 embedding 模型可用)── - let userEmbedding: number[] | null = null; - try { - const memEnabled = state.get('memoryEnabled', false); - const embedModel = state.get('embeddingModel', ''); - if (memEnabled && embedModel) { - const api = state.get(KEYS.API); - if (api?.embed) { - const result = await api.embed(embedModel, userMessage); - userEmbedding = result.embedding || result.embeddings?.[0] || null; - } - } - } catch { /* 语义匹配失败回退关键词 */ } - - const now = Date.now(); - const THIRTY_DAYS = 30 * 24 * 3600 * 1000; - - const scored = allSkills.map(skill => { - let score = 0; - - // 关键词匹配 - if (skill.trigger_keywords) { - const kwSet = new Set(skill.trigger_keywords.toLowerCase().split(',').map(k => k.trim())); - for (const kw of kwSet) { - if (kw && messageLower.includes(kw)) score += 0.3; - } - } - - // 名称和描述匹配 - const nameWords = skill.name.toLowerCase().split(/[\s→\-]+/); - for (const w of nameWords) { - if (w.length > 1 && messageWords.has(w)) score += 0.15; - } - - // 语义匹配:余弦相似度加权(主导分数) - if (userEmbedding && skill.summary) { - const skillEmbed = getCachedSkillEmbedding(skill.id); - if (skillEmbed) { - const sim = cosineSimilarity(userEmbedding, skillEmbed); - score += sim * 0.4; // 语义匹配主导 - } - } - - // 成功率加权 - const total = skill.success_count + skill.fail_count; - if (total > 0) score *= (0.5 + 0.5 * (skill.success_count / total)); - - // ── v1.1 未使用技能衰减 ── - if (skill.last_used_at) { - const daysSinceUse = (now - skill.last_used_at) / (24 * 3600 * 1000); - if (daysSinceUse > 30) score *= 0.5; // 30天未用降权50% - if (daysSinceUse > 90) score *= 0.3; // 90天未用几乎忽略 - } - - return { skill, score }; - }); - - // ── v1.1 技能链合并:相同前缀的链抽象为一个技能 ── - const merged = mergeSimilarSkills(scored); - - return merged - .filter(s => s.score >= MATCH_THRESHOLD) - .sort((a, b) => b.score - a.score) - .slice(0, limit) - .map(s => s.skill); - } catch { - return []; - } -} - -// ── 向量计算工具 ── - -/** 技能 embedding 缓存(LRU 简单实现) */ -const _skillEmbedCache = new Map(); - -function getCachedSkillEmbedding(skillId: string): number[] | null { - const cached = _skillEmbedCache.get(skillId); - if (cached && Date.now() - cached.time < 300_000) return cached.embedding; // 5分钟有效 - return null; -} - -export function cacheSkillEmbedding(skillId: string, embedding: number[]): void { - _skillEmbedCache.set(skillId, { embedding, time: Date.now() }); - // 限制缓存大小 - if (_skillEmbedCache.size > 200) { - const oldest = [..._skillEmbedCache.entries()].sort((a, b) => a[1].time - b[1].time)[0]; - if (oldest) _skillEmbedCache.delete(oldest[0]); - } -} - -function cosineSimilarity(a: number[], b: number[]): number { - if (!a || !b || a.length !== b.length) return 0; - let dot = 0, normA = 0, normB = 0; - for (let i = 0; i < a.length; i++) { - dot += a[i] * b[i]; - normA += a[i] * a[i]; - normB += b[i] * b[i]; - } - const denom = Math.sqrt(normA) * Math.sqrt(normB); - return denom === 0 ? 0 : dot / denom; -} - -// ── 技能链合并 ── - -interface ScoredSkill { skill: Skill; score: number; } - -/** - * v1.1 技能链合并:两个以上技能有相同前缀(前 2 步相同), - * 合并为一个抽象技能,分数取最高。 - */ -function mergeSimilarSkills(scored: ScoredSkill[]): ScoredSkill[] { - if (scored.length < 2) return scored; - - const result: ScoredSkill[] = []; - const used = new Set(); - - for (let i = 0; i < scored.length; i++) { - if (used.has(i)) continue; - const group: ScoredSkill[] = [scored[i]]; - - // 找同前缀的 - for (let j = i + 1; j < scored.length; j++) { - if (used.has(j)) continue; - if (getChainPrefix(scored[i].skill) === getChainPrefix(scored[j].skill)) { - group.push(scored[j]); - used.add(j); - } - } - - if (group.length >= 2) { - // 合并为一个抽象技能 - const best = group.reduce((a, b) => a.score > b.score ? a : b); - const chainNames = group.map(g => { - try { return (JSON.parse(g.skill.tool_chain) as ToolChainStep[]).map(s => s.name).join('→'); } - catch { return g.skill.name; } - }); - const mergedSkill: Skill = { - ...best.skill, - name: `${getChainPrefix(best.skill)} → ...`, - description: `合并了 ${group.length} 个相似技能: ${chainNames.join(' | ')}`, - summary: best.skill.summary, - }; - result.push({ skill: mergedSkill, score: best.score * 1.1 }); // 合并后略加分 - used.add(i); - } else { - result.push(scored[i]); - } - } - - return result; -} - -/** 获取技能链的前 2 步前缀(用于相似度判断) */ -function getChainPrefix(skill: Skill): string { - try { - const chain: ToolChainStep[] = JSON.parse(skill.tool_chain); - return chain.slice(0, 2).map(s => s.name).join('→'); - } catch { - return skill.name; - } -} - -/** - * 从匹配的技能构建 system prompt 上下文(Level 0:仅名称 + 描述索引) - * 完整工具链通过 skill_view 工具按需加载(Level 1) - */ -export function buildSkillContext(skills: Skill[]): string { - if (skills.length === 0) return ''; - - const parts = skills.map(skill => { - const total = skill.success_count + skill.fail_count; - const rate = total > 0 ? Math.round(skill.success_count / total * 100) : 100; - return `• ${skill.name} — ${skill.description}(成功率 ${rate}%)`; - }).filter(Boolean); - - if (parts.length === 0) return ''; - - return `【可用技能 — 以下技能曾成功完成过类似任务】\n${parts.join('\n')}\n\n提示:使用 skill_view 工具查看某个技能的完整工具链和参数提示。`; -} - -// ── 辅助函数 ── - -/** 生成工具步骤描述 */ -function describeToolStep(t: ToolCallRecord): string { - const args = t.arguments || {}; - switch (t.name) { - case 'read_file': return `读取文件 ${args.path || ''}`; - case 'write_file': return `写入文件 ${args.path || ''}`; - case 'list_directory': return `列出目录 ${args.path || ''}`; - case 'search_files': return `搜索文件 "${args.query || ''}"`; - case 'web_search': return `联网搜索 "${args.query || ''}"`; - case 'web_fetch': return `抓取网页 ${args.url || ''}`; - case 'run_command': return `执行命令 ${String(args.command || '').slice(0, 60)}`; - case 'git': return `Git ${args.action || ''}`; - case 'edit_file': return `编辑文件 ${args.path || ''}`; - case 'delete_file': return `删除 ${args.path || ''}`; - 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; - } -} - -/** 提取关键参数名 */ -function extractKeyArgs(t: ToolCallRecord): string { - const args = t.arguments || {}; - const keys: string[] = []; - if (args.path) keys.push('path'); - if (args.query) keys.push('query'); - if (args.url) keys.push('url'); - if (args.command) keys.push('command'); - if (args.action) keys.push('action'); - if (args.branch) keys.push('branch'); - return keys.join(', '); -} - -/** 从用户消息提取关键词(简单分词) */ -function extractKeywords(text: string): string { - // 移除常见停用词 - const stopWords = new Set(['我', '你', '的', '了', '是', '在', '有', '和', '就', '不', '人', '都', '一', '一个', '上', '也', '很', '到', '说', '要', '去', '你', '会', '着', '没有', '看', '好', '自己', '这', '请', '帮', '帮我', '可以', '怎么', '什么', '如何', 'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'to', 'of', 'and', 'in', 'that', 'have', 'for', 'it', 'with', 'as', 'this', 'from', 'by', 'on', 'not', 'but', 'what', 'all', 'can', 'had', 'how', 'do']); - - const words = text.toLowerCase() - .split(/[\s,,。!?、;:""''()\[\]【】<>《》]+/) - .filter(w => w.length > 1 && !stopWords.has(w)); - - // 取前 8 个关键词 - return [...new Set(words)].slice(0, 8).join(','); -} - -/** 生成技能名称 */ -function generateSkillName(chain: ToolChainStep[], userMessage: string): string { - const firstTool = chain[0].name; - const lastTool = chain[chain.length - 1].name; - - // 基于工具链模式命名 - const patterns: Record = { - 'web_search → web_fetch': '联网搜索并抓取详情', - 'list_directory → read_file': '浏览目录并读取文件', - 'search_files → read_file': '搜索并读取文件', - 'search_files → edit_file': '搜索并编辑文件', - 'search_files → write_file': '搜索并写入文件', - 'run_command → read_file': '执行命令并读取输出', - 'web_search → web_fetch → write_file': '搜索、抓取并保存', - }; - - const chainKey = chain.map(s => s.name).join(' → '); - if (patterns[chainKey]) return patterns[chainKey]; - - // 基于用户消息前 20 字 - const msgPreview = userMessage.slice(0, 20).replace(/[\r\n]/g, ' '); - return `${firstTool} → ... → ${lastTool}(${msgPreview})`; -} - -/** 生成技能描述 */ -function generateSkillDescription(chain: ToolChainStep[], sessionTitle: string): string { - const steps = chain.map((s, i) => `${i + 1}. ${s.description}`).join(';'); - return `从会话「${sessionTitle}」中提取。步骤:${steps}`; -} - -/** 生成技能简短摘要(Level 0,~100 tokens) */ -function generateSkillSummary(chain: ToolChainStep[], userMessage: string): string { - const toolNames = chain.map(s => s.name).join(' → '); - const msgPreview = userMessage.slice(0, 30).replace(/[\r\n]/g, ' '); - return `工具链: ${toolNames}。场景: ${msgPreview}`; -} - -// ── 渐进式技能加载 ── - -/** 列出所有技能(渐进式 Level 0:只返回名称、描述摘要、成功率) */ -export async function listSkills(): Promise> { - const bridge = window.metonaDesktop; - if (!bridge?.db?.getAllSkills) return []; - const allSkills: Skill[] = await bridge.db.getAllSkills() as any; - return (allSkills as any[]).map((skill: any) => { - const total = skill.success_count + skill.fail_count; - const rate = total > 0 ? Math.round(skill.success_count / total * 100) : 100; - let chainPreview = ''; - try { - const chain: ToolChainStep[] = JSON.parse(skill.tool_chain); - chainPreview = chain.map(s => s.name).join(' → '); - } catch { /* */ } - return { - name: skill.name, - description: skill.description, - summary: skill.summary || skill.description, - success_rate: `${rate}%`, - chain_preview: chainPreview - }; - }); -} - -/** 查看技能详情(渐进式 Level 1:返回完整工具链和参数提示) */ -export async function viewSkill(skillName: string): Promise<{ - success: boolean; - skill?: Skill & { chain: ToolChainStep[]; success_rate: string }; - error?: string; -}> { - const bridge = window.metonaDesktop; - if (!bridge?.db?.getAllSkills) return { success: false, error: '数据库不可用' }; - const allSkills: Skill[] = await bridge.db.getAllSkills() as any; - const skill = allSkills.find(s => - s.name.toLowerCase() === skillName.toLowerCase() || - s.name.toLowerCase().includes(skillName.toLowerCase()) - ); - if (!skill) return { success: false, error: `未找到技能: ${skillName}` }; - let chain: ToolChainStep[] = []; - try { chain = JSON.parse(skill.tool_chain); } catch { /* */ } - const total = skill.success_count + skill.fail_count; - const rate = total > 0 ? Math.round(skill.success_count / total * 100) : 100; - return { - success: true, - skill: { ...skill, chain, success_rate: `${rate}%` } - }; -} diff --git a/src/renderer/services/sub-agent.ts b/src/renderer/services/sub-agent.ts index cde6727..4101680 100644 --- a/src/renderer/services/sub-agent.ts +++ b/src/renderer/services/sub-agent.ts @@ -21,7 +21,6 @@ const SUB_AGENT_TOOL_WHITELIST = new Set([ 'browser_extract', 'browser_screenshot', 'memory_search', 'session_list', 'session_read', - 'skill_list', 'skill_view', ]); /** 从总工具列表中筛选子代理可用的 */ diff --git a/src/renderer/services/tool-registry.ts b/src/renderer/services/tool-registry.ts index 4d7692d..2f53022 100644 --- a/src/renderer/services/tool-registry.ts +++ b/src/renderer/services/tool-registry.ts @@ -446,31 +446,6 @@ export const TOOL_DEFINITIONS: ToolDefinition[] = [ } } }, - { - type: 'function', - function: { - name: 'skill_list', - description: 'List all available auto-generated skills. Shows name, description, and success rate. Use skill_view to see full details of a specific skill.', - parameters: { - type: 'object', - properties: {} - } - } - }, - { - type: 'function', - function: { - name: 'skill_view', - description: 'View full details of a specific skill including its tool chain, parameter hints, and usage statistics.', - parameters: { - type: 'object', - required: ['name'], - properties: { - name: { type: 'string', description: 'The skill name (or partial name) to view.' } - } - } - } - }, // ══════════════════════════════════════════════ // v4.3 新增工具:子代理委派 // ══════════════════════════════════════════════ @@ -478,7 +453,7 @@ export const TOOL_DEFINITIONS: ToolDefinition[] = [ type: 'function', function: { name: 'spawn_task', - description: 'Spawn a sub-agent to independently execute a task using read-only tools (file reading, web search, browser viewing, memory/session/skill queries). Use this to parallelize independent research or analysis sub-tasks. The model is configured in Settings and cannot be overridden per call.', + description: 'Spawn a sub-agent to independently execute a task using read-only tools (file reading, web search, browser viewing, memory/session queries). Use this to parallelize independent research or analysis sub-tasks. The model is configured in Settings and cannot be overridden per call.', parameters: { type: 'object', required: ['task'], @@ -740,7 +715,7 @@ let enabledTools: Set = new Set([ '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', 'spawn_task', + 'session_list', 'session_read', 'spawn_task', 'browser_open', 'browser_screenshot', 'browser_evaluate', 'browser_extract', 'browser_click', 'browser_type', 'browser_scroll', 'browser_close', 'datetime', 'calculator', @@ -903,19 +878,6 @@ export async function executeTool(toolName: string, args: Record Promise; exportSessions: () => Promise; importSessions: (data: unknown) => Promise<{ imported: number; skipped: number }>; - // v4.2 Skills - saveSkill: (skill: unknown) => Promise<{ success: boolean; id?: string; error?: string }>; - getAllSkills: () => Promise; - deleteSkill: (id: string) => Promise<{ success: boolean; error?: string }>; - clearAllSkills: () => Promise<{ success: boolean; error?: string }>; - searchSkills: (query: string, limit?: number) => Promise; - incrementSkillUsage: (id: string, success: boolean, durationMs: number) => Promise<{ success: boolean; error?: string }>; + getAllTokenStats(): Promise<{ sessions: Array<{ session_id: string;