feat: v0.16.15 - 修复工具定义重复、集成自适应压缩策略、批量轨迹写入、语义匹配增强
This commit is contained in:
@@ -14,7 +14,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://img.shields.io/badge/version-v0.16.14-E8734A?style=flat-square" alt="version">
|
<img src="https://img.shields.io/badge/version-v0.16.15-E8734A?style=flat-square" alt="version">
|
||||||
<img src="https://img.shields.io/badge/electron-33+-47848F?style=flat-square&logo=electron" alt="electron">
|
<img src="https://img.shields.io/badge/electron-33+-47848F?style=flat-square&logo=electron" alt="electron">
|
||||||
<img src="https://img.shields.io/badge/typescript-5.7+-3178C6?style=flat-square&logo=typescript" alt="typescript">
|
<img src="https://img.shields.io/badge/typescript-5.7+-3178C6?style=flat-square&logo=typescript" alt="typescript">
|
||||||
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="license">
|
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="license">
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
| 🌏 | **浏览器控制** | 打开网页 · 截图 · 执行 JS · 提取内容 · 点击 · 输入 · 滚动 · 关闭 |
|
| 🌏 | **浏览器控制** | 打开网页 · 截图 · 执行 JS · 提取内容 · 点击 · 输入 · 滚动 · 关闭 |
|
||||||
| 🖥️ | **工作空间面板** | 终端(增量流式输出)+ 文件浏览器,命令安全检查 |
|
| 🖥️ | **工作空间面板** | 终端(增量流式输出)+ 文件浏览器,命令安全检查 |
|
||||||
| 🔢 | **上下文长度手动控制** | 设置面板下拉选择(128K / 256K / 512K / 1M),默认 128K,模型栏显示当前配置值,下拉框中显示每个模型自身的上下文长度 |
|
| 🔢 | **上下文长度手动控制** | 设置面板下拉选择(128K / 256K / 512K / 1M),默认 128K,模型栏显示当前配置值,下拉框中显示每个模型自身的上下文长度 |
|
||||||
| 🗜️ | **智能上下文管理** | 滑动窗口 + Token 自动校准 + 消息重要性评分 + LLM 结构化 JSON 压缩,智能触发(120 条增量压缩 + 200 条硬上限) |
|
| 🗜️ | **智能上下文管理** | 滑动窗口 + Token 自动校准 + 消息重要性评分 + LLM 结构化 JSON 压缩,智能触发(120 条增量压缩 + 300 条硬上限) |
|
||||||
| ⏱️ | **智能超时保护** | Agent Loop 看门狗(可配,默认 30min)+ 流式总超时(可配,默认 300s)+ 工具 HTTP/MCP 超时可配 |
|
| ⏱️ | **智能超时保护** | Agent Loop 看门狗(可配,默认 30min)+ 流式总超时(可配,默认 300s)+ 工具 HTTP/MCP 超时可配 |
|
||||||
| 🪝 | **Hook 系统** | 4 阶段生命周期钩子(pre_tool / post_tool / post_iteration / pre_completion),内置安全检查、文件去重、自动计划追踪、变更审计、结果校验 |
|
| 🪝 | **Hook 系统** | 4 阶段生命周期钩子(pre_tool / post_tool / post_iteration / pre_completion),内置安全检查、文件去重、自动计划追踪、变更审计、结果校验 |
|
||||||
| 👥 | **子代理委派** | spawn_task 工具,独立上下文 + 超时保护 |
|
| 👥 | **子代理委派** | spawn_task 工具,独立上下文 + 超时保护 |
|
||||||
@@ -245,7 +245,7 @@ npm start
|
|||||||
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist
|
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist
|
||||||
```
|
```
|
||||||
|
|
||||||
产出:`release/Metona Ollama Setup v0.16.14.exe`
|
产出:`release/Metona Ollama Setup v0.16.15.exe`
|
||||||
|
|
||||||
## 🛠️ 常用命令
|
## 🛠️ 常用命令
|
||||||
|
|
||||||
@@ -286,7 +286,7 @@ npm run dist # 构建 Windows 安装包
|
|||||||
| 🌏 | **Browser Control** | Open pages · Screenshot · JS execution · Content extraction · Click · Type · Scroll · Close |
|
| 🌏 | **Browser Control** | Open pages · Screenshot · JS execution · Content extraction · Click · Type · Scroll · Close |
|
||||||
| 🖥️ | **Workspace Panel** | Terminal (incremental streaming) + file browser, command security checks |
|
| 🖥️ | **Workspace Panel** | Terminal (incremental streaming) + file browser, command security checks |
|
||||||
| 🔢 | **Manual Context Length** | Settings dropdown (128K / 256K / 512K / 1M), default 128K. Model bar shows configured value, dropdown shows per-model context length |
|
| 🔢 | **Manual Context Length** | Settings dropdown (128K / 256K / 512K / 1M), default 128K. Model bar shows configured value, dropdown shows per-model context length |
|
||||||
| 🗜️ | **Smart Context Manager** | Sliding window + Token auto-calibration + message importance scoring + LLM structured JSON compression, smart triggers (120 msg incremental + 200 msg hard limit) |
|
| 🗜️ | **Smart Context Manager** | Sliding window + Token auto-calibration + message importance scoring + LLM structured JSON compression, smart triggers (120 msg incremental + 300 msg hard limit) |
|
||||||
| ⏱️ | **Smart Timeout Protection** | Agent Loop watchdog (configurable, default 30min) + stream total timeout (configurable, default 300s) + configurable HTTP/MCP timeouts |
|
| ⏱️ | **Smart Timeout Protection** | Agent Loop watchdog (configurable, default 30min) + stream total timeout (configurable, default 300s) + configurable HTTP/MCP timeouts |
|
||||||
| 🪝 | **Hook System** | 4-phase lifecycle hooks (pre_tool / post_tool / post_iteration / pre_completion), built-in security check, file dedup, auto plan tracking, change audit, result validation |
|
| 🪝 | **Hook System** | 4-phase lifecycle hooks (pre_tool / post_tool / post_iteration / pre_completion), built-in security check, file dedup, auto plan tracking, change audit, result validation |
|
||||||
| 👥 | **Sub-agent Delegation** | spawn_task tool, isolated context + timeout protection |
|
| 👥 | **Sub-agent Delegation** | spawn_task tool, isolated context + timeout protection |
|
||||||
@@ -485,7 +485,7 @@ npm start
|
|||||||
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist
|
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist
|
||||||
```
|
```
|
||||||
|
|
||||||
Output: `release/Metona Ollama Setup v0.16.14.exe`
|
Output: `release/Metona Ollama Setup v0.16.15.exe`
|
||||||
|
|
||||||
## 🛠️ Common Commands
|
## 🛠️ Common Commands
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -205,7 +205,7 @@ INIT → THINKING → PARSING → EXECUTING → OBSERVING → REFLECTING → (CO
|
|||||||
- 最大轮次:85(默认,设置面板可调)
|
- 最大轮次:85(默认,设置面板可调)
|
||||||
- 自动重试:2 次(MAX_RETRIES)
|
- 自动重试:2 次(MAX_RETRIES)
|
||||||
- 看门狗超时:30 分钟(默认,可配)
|
- 看门狗超时:30 分钟(默认,可配)
|
||||||
- 上下文硬上限:500 条消息
|
- 上下文硬上限:300 条消息
|
||||||
- 流式超时:可配(默认 300s,0=禁用)
|
- 流式超时:可配(默认 300s,0=禁用)
|
||||||
- HTTP 超时:可配(默认 30s)
|
- HTTP 超时:可配(默认 30s)
|
||||||
- MCP 超时:可配(默认 60s)
|
- MCP 超时:可配(默认 60s)
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "metona-ollama-desktop",
|
"name": "metona-ollama-desktop",
|
||||||
"version": "0.16.14",
|
"version": "0.16.15",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "metona-ollama-desktop",
|
"name": "metona-ollama-desktop",
|
||||||
"version": "0.16.14",
|
"version": "0.16.15",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ffmpeg-static": "^5.2.0",
|
"ffmpeg-static": "^5.2.0",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "metona-ollama-desktop",
|
"name": "metona-ollama-desktop",
|
||||||
"version": "0.16.14",
|
"version": "0.16.15",
|
||||||
"description": "Metona Ollama - TypeScript + Electron 桌面 AI 聊天客户端",
|
"description": "Metona Ollama - TypeScript + Electron 桌面 AI 聊天客户端",
|
||||||
"main": "dist/main/main.js",
|
"main": "dist/main/main.js",
|
||||||
"author": "thzxx",
|
"author": "thzxx",
|
||||||
|
|||||||
@@ -365,6 +365,22 @@ export function saveTrace(trace: TraceRow): string {
|
|||||||
return trace.id;
|
return trace.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 批量保存轨迹 — 只触发一次 persist,避免逐条写盘 */
|
||||||
|
export function saveTracesBatch(traces: TraceRow[]): number {
|
||||||
|
if (!traces.length) return 0;
|
||||||
|
const d = getDb();
|
||||||
|
runTransaction(d, () => {
|
||||||
|
for (const trace of traces) {
|
||||||
|
runExec(d, `INSERT OR REPLACE INTO traces (id, session_id, step_index, thought, action, action_input, observation, loop_count, error_pattern, created_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
[trace.id, trace.session_id, trace.step_index, trace.thought, trace.action, trace.action_input, trace.observation, trace.loop_count, trace.error_pattern, trace.created_at]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
persist(); // 只写盘一次
|
||||||
|
return traces.length;
|
||||||
|
}
|
||||||
|
|
||||||
export function getTracesBySession(sessionId: string): TraceRow[] {
|
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[];
|
return queryAll(getDb(), 'SELECT * FROM traces WHERE session_id = ? ORDER BY step_index ASC', [sessionId]) as unknown as TraceRow[];
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -12,7 +12,7 @@ import {
|
|||||||
initDatabase, saveSession, getSession, getAllSessions, deleteSession, clearAllSessions,
|
initDatabase, saveSession, getSession, getAllSessions, deleteSession, clearAllSessions,
|
||||||
saveMessage, getMessagesBySession,
|
saveMessage, getMessagesBySession,
|
||||||
saveSetting, getSetting, saveSettingsBatch,
|
saveSetting, getSetting, saveSettingsBatch,
|
||||||
saveTrace, getTracesBySession,
|
saveTrace, saveTracesBatch, getTracesBySession,
|
||||||
exportAllSessions, importSessions,
|
exportAllSessions, importSessions,
|
||||||
getAllSessionsTokenStats
|
getAllSessionsTokenStats
|
||||||
} from './db/sqlite.js';
|
} from './db/sqlite.js';
|
||||||
@@ -383,6 +383,10 @@ export async function setupIPC(): Promise<void> {
|
|||||||
try { return { success: true, id: saveTrace(trace) }; }
|
try { return { success: true, id: saveTrace(trace) }; }
|
||||||
catch (err) { return { success: false, error: (err as Error).message }; }
|
catch (err) { return { success: false, error: (err as Error).message }; }
|
||||||
});
|
});
|
||||||
|
ipcMain.handle('db:saveTracesBatch', (_, traces) => {
|
||||||
|
try { return { success: true, count: saveTracesBatch(traces) }; }
|
||||||
|
catch (err) { return { success: false, error: (err as Error).message }; }
|
||||||
|
});
|
||||||
ipcMain.handle('db:getTraces', (_, sessionId) => {
|
ipcMain.handle('db:getTraces', (_, sessionId) => {
|
||||||
try { return getTracesBySession(sessionId); }
|
try { return getTracesBySession(sessionId); }
|
||||||
catch { return []; }
|
catch { return []; }
|
||||||
|
|||||||
+1
-1
@@ -101,7 +101,7 @@ export function createMenu(): void {
|
|||||||
dialog.showMessageBox(mainWindow!, {
|
dialog.showMessageBox(mainWindow!, {
|
||||||
type: 'info',
|
type: 'info',
|
||||||
title: '关于 Metona Ollama',
|
title: '关于 Metona Ollama',
|
||||||
message: 'Metona Ollama Desktop v0.16.14',
|
message: 'Metona Ollama Desktop v0.16.15',
|
||||||
detail: 'TypeScript + Electron Ollama AI 聊天客户端\n\nhttps://gitee.com/thzxx/metona-ollama',
|
detail: 'TypeScript + Electron Ollama AI 聊天客户端\n\nhttps://gitee.com/thzxx/metona-ollama',
|
||||||
icon: getIconPath()
|
icon: getIconPath()
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-2
@@ -65,9 +65,8 @@ contextBridge.exposeInMainWorld('metonaDesktop', {
|
|||||||
getMessages: (sessionId: string) => ipcRenderer.invoke('db:getMessages', sessionId),
|
getMessages: (sessionId: string) => ipcRenderer.invoke('db:getMessages', sessionId),
|
||||||
saveSetting: (key: string, value: unknown) => ipcRenderer.invoke('db:saveSetting', key, value),
|
saveSetting: (key: string, value: unknown) => ipcRenderer.invoke('db:saveSetting', key, value),
|
||||||
getSetting: (key: string, defaultValue?: unknown) => ipcRenderer.invoke('db:getSetting', key, defaultValue),
|
getSetting: (key: string, defaultValue?: unknown) => ipcRenderer.invoke('db:getSetting', key, defaultValue),
|
||||||
saveToolCall: (tc: unknown) => ipcRenderer.invoke('db:saveToolCall', tc),
|
|
||||||
getToolCalls: (sessionId: string) => ipcRenderer.invoke('db:getToolCalls', sessionId),
|
|
||||||
saveTrace: (trace: unknown) => ipcRenderer.invoke('db:saveTrace', trace),
|
saveTrace: (trace: unknown) => ipcRenderer.invoke('db:saveTrace', trace),
|
||||||
|
saveTracesBatch: (traces: unknown[]) => ipcRenderer.invoke('db:saveTracesBatch', traces),
|
||||||
getTraces: (sessionId: string) => ipcRenderer.invoke('db:getTraces', sessionId),
|
getTraces: (sessionId: string) => ipcRenderer.invoke('db:getTraces', sessionId),
|
||||||
exportSessions: () => ipcRenderer.invoke('db:exportSessions'),
|
exportSessions: () => ipcRenderer.invoke('db:exportSessions'),
|
||||||
importSessions: (data: unknown) => ipcRenderer.invoke('db:importSessions', data),
|
importSessions: (data: unknown) => ipcRenderer.invoke('db:importSessions', data),
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
<img class="logo" src="./assets/icons/llama.png" alt="logo" />
|
<img class="logo" src="./assets/icons/llama.png" alt="logo" />
|
||||||
<span class="app-title">Metona Ollama</span>
|
<span class="app-title">Metona Ollama</span>
|
||||||
<span class="app-version">v0.16.14</span>
|
<span class="app-version">v0.16.15</span>
|
||||||
<button class="icon-btn help-btn" id="btnHelp" title="使用帮助">
|
<button class="icon-btn help-btn" id="btnHelp" title="使用帮助">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/>
|
<circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/>
|
||||||
@@ -446,7 +446,7 @@
|
|||||||
<div class="help-section"><h4>🧠 Agent 记忆系统</h4><ul><li>记忆存储在工作空间 <strong>MEMORY.md</strong> 文件,受严格路径保护,仅 <code>memory</code> 工具可读写</li><li>新对话时自动检索相关记忆注入 AI 上下文,让 AI "记住"你</li><li>对话结束时 AI 自动提取有价值的用户信息保存(多层质量过滤,宁缺毋滥)</li><li><strong>memory 工具</strong>(5 个 action):search(关键词搜索)/ add(添加)/ replace(替换)/ remove(删除)/ read_all(读取全部)</li><li>点击顶部 🧠 按钮打开记忆面板:查看、添加、删除记忆条目</li><li><strong>记忆容量上限 500 条</strong>,超限时自动清理低价值条目(规则类型受保护)</li><li>写入前自动安全扫描(prompt injection / 敏感信息 / 不可见字符检测)</li><li>应用启动时自动校验 MEMORY.md 格式,格式错误自动备份重建</li></ul></div>
|
<div class="help-section"><h4>🧠 Agent 记忆系统</h4><ul><li>记忆存储在工作空间 <strong>MEMORY.md</strong> 文件,受严格路径保护,仅 <code>memory</code> 工具可读写</li><li>新对话时自动检索相关记忆注入 AI 上下文,让 AI "记住"你</li><li>对话结束时 AI 自动提取有价值的用户信息保存(多层质量过滤,宁缺毋滥)</li><li><strong>memory 工具</strong>(5 个 action):search(关键词搜索)/ add(添加)/ replace(替换)/ remove(删除)/ read_all(读取全部)</li><li>点击顶部 🧠 按钮打开记忆面板:查看、添加、删除记忆条目</li><li><strong>记忆容量上限 500 条</strong>,超限时自动清理低价值条目(规则类型受保护)</li><li>写入前自动安全扫描(prompt injection / 敏感信息 / 不可见字符检测)</li><li>应用启动时自动校验 MEMORY.md 格式,格式错误自动备份重建</li></ul></div>
|
||||||
<div class="help-section"><h4>📋 Plan Mode(计划模式)</h4><ul><li>点击输入框上方 📋 按钮开启 <strong>Plan Mode</strong>(开关式)</li><li>开启后,AI <strong>先生成执行计划</strong>(Markdown 渲染弹窗),用户批准后才开始执行</li><li>计划批准后自动初始化追踪器,每个步骤完成后调用 <code>plan_track</code> 工具标记进度</li><li>系统提示词自动注入当前进度状态,AI 始终知道还剩多少步未完成</li><li><strong>多步骤任务防遗忘</strong>:自动检测用户请求中的动作动词,对比已完成步骤,注入提醒</li><li>关闭 Plan Mode 后恢复正常 Agent Loop 模式</li><li><strong>仅 Plan 模式</strong> 下 plan_track 工具可见,避免污染普通模式的工具列表</li></ul></div>
|
<div class="help-section"><h4>📋 Plan Mode(计划模式)</h4><ul><li>点击输入框上方 📋 按钮开启 <strong>Plan Mode</strong>(开关式)</li><li>开启后,AI <strong>先生成执行计划</strong>(Markdown 渲染弹窗),用户批准后才开始执行</li><li>计划批准后自动初始化追踪器,每个步骤完成后调用 <code>plan_track</code> 工具标记进度</li><li>系统提示词自动注入当前进度状态,AI 始终知道还剩多少步未完成</li><li><strong>多步骤任务防遗忘</strong>:自动检测用户请求中的动作动词,对比已完成步骤,注入提醒</li><li>关闭 Plan Mode 后恢复正常 Agent Loop 模式</li><li><strong>仅 Plan 模式</strong> 下 plan_track 工具可见,避免污染普通模式的工具列表</li></ul></div>
|
||||||
<div class="help-section"><h4>🛡️ 抗幻觉 & 稳定性</h4><ul><li><strong>5 层防御体系</strong>:系统提示词加固 → 任务感知 → 中途幻觉检测(中英双语规则覆盖全部工具类别)→ 进度锚点 → 完成闸门(6 项检查,幻觉/注入 → 阻断,质量/效率 → 咨询)</li><li><strong>完成闸门</strong>:对话结束前自动审查 AI 回复,检测工具幻觉和 prompt injection,阻断有问题的回复</li><li><strong>中英双语检测规则</strong>:覆盖中英文模型输出,防止 AI 声称执行了未调用的工具</li><li><strong>智能重试机制</strong>:永久错误(文件不存在/权限拒绝)立即返回,瞬态错误(网络/超时)指数退避最多重试 2 次</li><li><strong>看门狗超时</strong>:设置面板可配置全局超时(默认 30 分钟),AI 卡死或无限循环时自动中止</li><li><strong>流式总超时</strong>:可配置超时(默认 300s),Ollama 卡死不再永久阻塞</li><li><strong>中止保护</strong>:所有状态处理器 + 重试循环均检查中止信号,点击 ■ 按钮立即生效</li><li><strong>上下文硬上限</strong>:200 条消息强制压缩 + 120 条增量压缩,防止 OOM</li></ul></div>
|
<div class="help-section"><h4>🛡️ 抗幻觉 & 稳定性</h4><ul><li><strong>5 层防御体系</strong>:系统提示词加固 → 任务感知 → 中途幻觉检测(中英双语规则覆盖全部工具类别)→ 进度锚点 → 完成闸门(6 项检查,幻觉/注入 → 阻断,质量/效率 → 咨询)</li><li><strong>完成闸门</strong>:对话结束前自动审查 AI 回复,检测工具幻觉和 prompt injection,阻断有问题的回复</li><li><strong>中英双语检测规则</strong>:覆盖中英文模型输出,防止 AI 声称执行了未调用的工具</li><li><strong>智能重试机制</strong>:永久错误(文件不存在/权限拒绝)立即返回,瞬态错误(网络/超时)指数退避最多重试 2 次</li><li><strong>看门狗超时</strong>:设置面板可配置全局超时(默认 30 分钟),AI 卡死或无限循环时自动中止</li><li><strong>流式总超时</strong>:可配置超时(默认 300s),Ollama 卡死不再永久阻塞</li><li><strong>中止保护</strong>:所有状态处理器 + 重试循环均检查中止信号,点击 ■ 按钮立即生效</li><li><strong>上下文硬上限</strong>:200 条消息强制压缩 + 120 条增量压缩,防止 OOM</li></ul></div>
|
||||||
<div class="help-section"><h4>🤖 Agent Loop 增强</h4><ul><li><strong>🎬 视频上传</strong>:支持上传 .mp4/.avi/.mov/.mkv/.webm 等视频(≤10MB),自动 1fps 提取帧序列(带时间戳),多模态模型原生理解视频时序关系</li><li><strong>8 状态机</strong>:INIT→THINKING→PARSING→EXECUTING→OBSERVING→REFLECTING→COMPRESSING→TERMINATED,每个状态独立的中止检查和处理</li><li><strong>智能上下文压缩</strong>:滑动窗口 + LLM结构化JSON摘要,120条触发增量压缩 + 200条硬上限强制压缩</li><li><strong>流式总超时</strong>:可配置(默认300s),Ollama 假死不再永久阻塞</li><li><strong>HTTP/MCP 超时可配</strong>:设置面板可分别调整 HTTP(默认30s)和 MCP(默认60s)超时</li><li><strong>智能工具调度</strong>:路径依赖检测自动串行化(write→read/create→write),只读工具并行执行</li><li><strong>工具缓存 TTL</strong>:搜索5分钟/网页10分钟/文件30分钟/git 30秒,默认60秒过期,不再永久缓存</li><li><strong>Plan Mode 断点续传</strong>:中止后可恢复未完成的计划,进度自动保存</li><li><strong>Token 感知迭代预算</strong>:上下文使用率>80%时自动缩减剩余轮次到3轮</li><li><strong>跨会话工具上下文</strong>:新对话自动注入上一轮已执行的工具调用及结果(role:tool 消息),AI 不会重复执行已完成的操作</li><li><strong>旧工具结果智能截断</strong>:超过10轮后自动截断到2000字符,优先在JSON边界处截断</li></ul></div>
|
<div class="help-section"><h4>🤖 Agent Loop 增强</h4><ul><li><strong>🎬 视频上传</strong>:支持上传 .mp4/.avi/.mov/.mkv/.webm 等视频(≤10MB),自动 1fps 提取帧序列(带时间戳),多模态模型原生理解视频时序关系</li><li><strong>8 状态机</strong>:INIT→THINKING→PARSING→EXECUTING→OBSERVING→REFLECTING→COMPRESSING→TERMINATED,每个状态独立的中止检查和处理</li><li><strong>智能上下文压缩</strong>:滑动窗口 + LLM结构化JSON摘要,120条触发增量压缩 + 300条硬上限强制压缩</li><li><strong>流式总超时</strong>:可配置(默认300s),Ollama 假死不再永久阻塞</li><li><strong>HTTP/MCP 超时可配</strong>:设置面板可分别调整 HTTP(默认30s)和 MCP(默认60s)超时</li><li><strong>智能工具调度</strong>:路径依赖检测自动串行化(write→read/create→write),只读工具并行执行</li><li><strong>工具缓存 TTL</strong>:搜索5分钟/网页10分钟/文件30分钟/git 30秒,默认60秒过期,不再永久缓存</li><li><strong>Plan Mode 断点续传</strong>:中止后可恢复未完成的计划,进度自动保存</li><li><strong>Token 感知迭代预算</strong>:上下文使用率>80%时自动缩减剩余轮次到3轮</li><li><strong>跨会话工具上下文</strong>:新对话自动注入上一轮已执行的工具调用及结果(role:tool 消息),AI 不会重复执行已完成的操作</li><li><strong>旧工具结果智能截断</strong>:超过10轮后自动截断到2000字符,优先在JSON边界处截断</li></ul></div>
|
||||||
<div class="help-section"><h4>🔌 MCP(Model Context Protocol)</h4><ul><li>支持连接外部 MCP Server,动态扩展工具能力</li><li>设置面板可添加/启用/禁用/删除 MCP 服务器</li><li>MCP 工具以 <code>mcp_{server}__{tool}</code> 前缀注册,与内置工具统一调度</li><li>启动时自动连接已启用的 MCP 服务器</li></ul></div>
|
<div class="help-section"><h4>🔌 MCP(Model Context Protocol)</h4><ul><li>支持连接外部 MCP Server,动态扩展工具能力</li><li>设置面板可添加/启用/禁用/删除 MCP 服务器</li><li>MCP 工具以 <code>mcp_{server}__{tool}</code> 前缀注册,与内置工具统一调度</li><li>启动时自动连接已启用的 MCP 服务器</li></ul></div>
|
||||||
<div class="help-section"><h4>🔍 SearXNG 元搜索引擎</h4><ul><li>点击顶部 🔍 按钮打开配置面板,可接入自部署的 SearXNG 实例</li><li><strong>JSON 模式</strong>:调用 SearXNG JSON API,聚合 70+ 引擎结果,结构化解析</li><li><strong>HTML 模式</strong>:获取原始搜索结果页面,交由 AI 自行分析提取信息</li><li>支持认证 Key(HTTP Header Authorization),保护私有实例</li><li>启用后替代内置四引擎方案;关闭即回退,无缝切换</li><li>所有参数(引擎、语言、安全搜索、时间范围等)均可独立配置</li></ul></div>
|
<div class="help-section"><h4>🔍 SearXNG 元搜索引擎</h4><ul><li>点击顶部 🔍 按钮打开配置面板,可接入自部署的 SearXNG 实例</li><li><strong>JSON 模式</strong>:调用 SearXNG JSON API,聚合 70+ 引擎结果,结构化解析</li><li><strong>HTML 模式</strong>:获取原始搜索结果页面,交由 AI 自行分析提取信息</li><li>支持认证 Key(HTTP Header Authorization),保护私有实例</li><li>启用后替代内置四引擎方案;关闭即回退,无缝切换</li><li>所有参数(引擎、语言、安全搜索、时间范围等)均可独立配置</li></ul></div>
|
||||||
<div class="help-section"><h4>📋 自定义文件(SOUL.md / AGENT.md / USER.md)</h4><ul><li>在工作空间目录创建以下文件即可自定义 AI 行为,修改后下一轮对话立即生效</li><li><strong>SOUL.md</strong> — AI 身份、性格、行为准则(<strong>永远不可被压缩</strong>,注入为最高优先级系统提示词)</li><li><strong>AGENT.md</strong> — 工具调用规则、链式调用模式、核心约束(仅从工作空间加载,有则注入,无则不注入)</li><li><strong>USER.md</strong> — 用户画像:技术栈、偏好、习惯等个人信息,AI 在对话中自动参考(仅工作空间,无内置默认)</li><li>可在 AI 回复顶部的 📋 系统提示词卡片中查看实际注入的完整上下文</li><li>SOUL.md 删除工作空间文件可恢复内置默认版本;AGENT.md / USER.md 无内置版,工作空间不存在则不注入</li></ul></div>
|
<div class="help-section"><h4>📋 自定义文件(SOUL.md / AGENT.md / USER.md)</h4><ul><li>在工作空间目录创建以下文件即可自定义 AI 行为,修改后下一轮对话立即生效</li><li><strong>SOUL.md</strong> — AI 身份、性格、行为准则(<strong>永远不可被压缩</strong>,注入为最高优先级系统提示词)</li><li><strong>AGENT.md</strong> — 工具调用规则、链式调用模式、核心约束(仅从工作空间加载,有则注入,无则不注入)</li><li><strong>USER.md</strong> — 用户画像:技术栈、偏好、习惯等个人信息,AI 在对话中自动参考(仅工作空间,无内置默认)</li><li>可在 AI 回复顶部的 📋 系统提示词卡片中查看实际注入的完整上下文</li><li>SOUL.md 删除工作空间文件可恢复内置默认版本;AGENT.md / USER.md 无内置版,工作空间不存在则不注入</li></ul></div>
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ import {
|
|||||||
getTrendAwareCompressThreshold,
|
getTrendAwareCompressThreshold,
|
||||||
// R100: Token 使用统计报告
|
// R100: Token 使用统计报告
|
||||||
generateTokenReport, formatTokenReport,
|
generateTokenReport, formatTokenReport,
|
||||||
|
// R111: 自适应压缩策略选择
|
||||||
|
chooseCompressionStrategy,
|
||||||
} from './context-manager.js';
|
} from './context-manager.js';
|
||||||
import { executeHooks } from './hooks.js';
|
import { executeHooks } from './hooks.js';
|
||||||
import { recordIteration, recordToolCall, startSessionMetrics, endSessionMetrics } from './agent-metrics.js';
|
import { recordIteration, recordToolCall, startSessionMetrics, endSessionMetrics } from './agent-metrics.js';
|
||||||
@@ -984,36 +986,38 @@ async function flushTraces(): Promise<void> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const trace of batch) {
|
// 构建所有轨迹条目
|
||||||
const entry = {
|
const entries = batch.map(trace => ({
|
||||||
id: String(trace.id || `trace_${generateId()}`),
|
id: String(trace.id || `trace_${generateId()}`),
|
||||||
session_id: trace.sessionId,
|
session_id: trace.sessionId,
|
||||||
step_index: trace.stepIndex,
|
step_index: trace.stepIndex,
|
||||||
thought: trace.thought,
|
thought: trace.thought,
|
||||||
action: trace.action,
|
action: trace.action,
|
||||||
action_input: trace.actionInput,
|
action_input: trace.actionInput,
|
||||||
observation: trace.observation,
|
observation: trace.observation,
|
||||||
loop_count: trace.loopCount,
|
loop_count: trace.loopCount,
|
||||||
error_pattern: trace.errorPattern || null,
|
error_pattern: trace.errorPattern || null,
|
||||||
created_at: trace.createdAt
|
created_at: trace.createdAt
|
||||||
};
|
}));
|
||||||
let saved = false;
|
|
||||||
// 重试最多 TRACE_MAX_RETRIES 次
|
// 批量保存:只触发一次 persist,而非逐条写盘
|
||||||
for (let attempt = 0; attempt < TRACE_MAX_RETRIES; attempt++) {
|
let saved = false;
|
||||||
try {
|
for (let attempt = 0; attempt < TRACE_MAX_RETRIES; attempt++) {
|
||||||
await bridge.db.saveTrace(entry);
|
try {
|
||||||
|
const result = await bridge.db.saveTracesBatch(entries);
|
||||||
|
if (result.success) {
|
||||||
saved = true;
|
saved = true;
|
||||||
break;
|
break;
|
||||||
} catch {
|
}
|
||||||
if (attempt < TRACE_MAX_RETRIES - 1) {
|
} catch {
|
||||||
await new Promise(r => setTimeout(r, 200 * (attempt + 1)));
|
if (attempt < TRACE_MAX_RETRIES - 1) {
|
||||||
}
|
await new Promise(r => setTimeout(r, 200 * (attempt + 1)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// R9: 所有重试都失败时,降级到 localStorage
|
}
|
||||||
if (!saved) {
|
// R9: 所有重试都失败时,降级到 localStorage
|
||||||
_fallbackSaveTraces([trace]);
|
if (!saved) {
|
||||||
}
|
_fallbackSaveTraces(batch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2443,23 +2447,57 @@ async function handleCompressing(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
logInfo('COMPRESSING: 上下文压缩触发');
|
// R111: 自适应压缩策略 — 根据上下文压力和消息特征选择最优压缩路径
|
||||||
const compressAC = state.get<AbortController | null>(KEYS.ABORT_CONTROLLER) || new AbortController();
|
const pressureInfo = getContextPressureLevel(ctx.messages, numCtx);
|
||||||
try {
|
const decision = chooseCompressionStrategy(ctx.messages, numCtx, pressureInfo.level);
|
||||||
const compressed = await compressWithLLM(ctx.messages, api, model, { abortController: compressAC });
|
logInfo(`COMPRESSING: 上下文压缩触发 — 策略=${decision.strategy}, 原因=${decision.reason}`);
|
||||||
// P1-C4 修复:原条件用 OR(消息数减少 OR token 减少),可能接受 token 增加的结果。
|
|
||||||
// 改为 AND:只有消息数减少且 token 减少时才接受,确保压缩真正生效
|
if (decision.strategy === 'skip') {
|
||||||
|
// 压力很低,跳过压缩
|
||||||
|
logInfo('COMPRESSING: 跳过压缩(上下文压力低)');
|
||||||
|
} else if (decision.strategy === 'fast' || decision.strategy === 'medium') {
|
||||||
|
// 快速/中等压缩:本地操作,无需 LLM 调用
|
||||||
|
const beforeLen = ctx.messages.length;
|
||||||
const beforeTokens = estimateTokens(ctx.messages.map(m => m.content || '').join(''));
|
const beforeTokens = estimateTokens(ctx.messages.map(m => m.content || '').join(''));
|
||||||
const afterTokens = estimateTokens(compressed.map(m => m.content || '').join(''));
|
|
||||||
if (compressed.length < ctx.messages.length && afterTokens < beforeTokens) {
|
// fast 策略:先归档旧工具结果(>1500 字符的工具消息精简为引用)
|
||||||
ctx.messages.length = 0;
|
if (decision.strategy === 'fast') {
|
||||||
ctx.messages.push(...compressed);
|
ctx.messages = ctx.messages.map(m => compactOldToolResult(m));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用:合并连续相同角色消息,减少消息条数开销
|
||||||
|
ctx.messages = mergeConsecutiveMessages(ctx.messages);
|
||||||
|
|
||||||
|
const afterLen = ctx.messages.length;
|
||||||
|
const afterTokens = estimateTokens(ctx.messages.map(m => m.content || '').join(''));
|
||||||
|
if (afterLen < beforeLen || afterTokens < beforeTokens) {
|
||||||
actuallyCompressed = true;
|
actuallyCompressed = true;
|
||||||
logSuccess('COMPRESSING: 完成');
|
logSuccess(`COMPRESSING: ${decision.strategy}压缩完成 — ${beforeLen}→${afterLen} 条, ${beforeTokens}→${afterTokens} tokens`);
|
||||||
|
} else {
|
||||||
|
logInfo('COMPRESSING: 快速压缩未产生效果,保留原消息');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// llm 策略:LLM 结构化摘要压缩(最重,效果最好)
|
||||||
|
const compressAC = state.get<AbortController | null>(KEYS.ABORT_CONTROLLER) || new AbortController();
|
||||||
|
try {
|
||||||
|
const compressed = await compressWithLLM(ctx.messages, api, model, { abortController: compressAC });
|
||||||
|
// P1-C4 修复:原条件用 OR(消息数减少 OR token 减少),可能接受 token 增加的结果。
|
||||||
|
// 改为 AND:只有消息数减少且 token 减少时才接受,确保压缩真正生效
|
||||||
|
const beforeTokens = estimateTokens(ctx.messages.map(m => m.content || '').join(''));
|
||||||
|
const afterTokens = estimateTokens(compressed.map(m => m.content || '').join(''));
|
||||||
|
if (compressed.length < ctx.messages.length && afterTokens < beforeTokens) {
|
||||||
|
ctx.messages.length = 0;
|
||||||
|
ctx.messages.push(...compressed);
|
||||||
|
actuallyCompressed = true;
|
||||||
|
logSuccess('COMPRESSING: LLM 压缩完成');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if ((err as Error).name === 'AbortError') throw err;
|
||||||
|
logWarn('COMPRESSING: LLM 压缩失败,回退到快速压缩', (err as Error).message);
|
||||||
|
// 回退:至少做消息合并
|
||||||
|
ctx.messages = mergeConsecutiveMessages(ctx.messages);
|
||||||
|
actuallyCompressed = true;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
if ((err as Error).name === 'AbortError') throw err;
|
|
||||||
logWarn('COMPRESSING: 失败', (err as Error).message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// P1 修复: 如果压缩后内容/工具调用仍为空(说明是从主循环紧急压缩路径进入,
|
// P1 修复: 如果压缩后内容/工具调用仍为空(说明是从主循环紧急压缩路径进入,
|
||||||
|
|||||||
@@ -540,272 +540,6 @@ TIP: Use remove_batch when deleting multiple entries — it's much more efficien
|
|||||||
parameters: { type: 'object', properties: {} }
|
parameters: { type: 'object', properties: {} }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: 'function',
|
|
||||||
function: {
|
|
||||||
name: 'calculator',
|
|
||||||
description: 'Safely evaluate a mathematical expression. Supports + - * / ** % () and floating-point numbers. Uses a pure JS recursive descent parser — no eval(), CSP-safe. Returns the numeric result.',
|
|
||||||
parameters: {
|
|
||||||
type: 'object',
|
|
||||||
required: ['expression'],
|
|
||||||
properties: {
|
|
||||||
expression: { type: 'string', description: 'The mathematical expression to evaluate (e.g., "(3 + 5) * 2 ** 3"). Max 500 characters.' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'function',
|
|
||||||
function: {
|
|
||||||
name: 'web_search',
|
|
||||||
description: 'Search the web with auto-fetch. Always use this when the user asks for information, news, facts, or current data. Never rely on training data or past conversation results — call this tool every time the user asks to search. Set fetch_top to auto-fetch full page content from the top N results.',
|
|
||||||
parameters: {
|
|
||||||
type: 'object',
|
|
||||||
required: ['query', 'fetch_top'],
|
|
||||||
properties: {
|
|
||||||
query: { type: 'string', description: 'The search query. Be specific and concise for best results.' },
|
|
||||||
max_results: { type: 'integer', description: 'Maximum number of results to return. Default: 30, max: 30.' },
|
|
||||||
time_range: { type: 'string', enum: ['day', 'week', 'month', 'year'], description: 'Filter results by time. Supported by Bing and Google. Use for recent news, latest version, etc.' },
|
|
||||||
enhance_snippets: { type: 'boolean', description: 'Auto-fetch detailed snippet for results with too-short descriptions. Default: true.' },
|
|
||||||
fetch_top: { type: 'integer', description: 'Number of top results to auto-fetch full content for. Min: 3. Results shorter than this are useless — snippets lack detail.' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// ══════════════════════════════════════════════
|
|
||||||
// 记忆工具(统一入口,读写工作空间 MEMORY.md)
|
|
||||||
// ══════════════════════════════════════════════
|
|
||||||
{
|
|
||||||
type: 'function',
|
|
||||||
function: {
|
|
||||||
name: 'memory',
|
|
||||||
description: `Manage agent memories stored in MEMORY.md. Usage by action:
|
|
||||||
- search: {"action":"search","query":"keywords","limit":8}
|
|
||||||
- add: {"action":"add","type":"fact","content":"the memory content","importance":8,"tags":["tag1","tag2"]} ← type and content are REQUIRED for add!
|
|
||||||
- replace: {"action":"replace","old_text":"unique substring","new_content":"replacement"}
|
|
||||||
- remove: {"action":"remove","old_text":"unique substring"}
|
|
||||||
- remove_batch: {"action":"remove_batch","old_texts":["substring1","substring2","substring3"]} ← Batch delete multiple entries in one call
|
|
||||||
- read_all: {"action":"read_all"}
|
|
||||||
|
|
||||||
CRITICAL: For add action, you MUST include both "type" (fact/preference/rule) and "content" fields. If you omit them, the call will fail.
|
|
||||||
TIP: Use remove_batch when deleting multiple entries — it's much more efficient than calling remove repeatedly.`,
|
|
||||||
parameters: {
|
|
||||||
type: 'object',
|
|
||||||
required: ['action'],
|
|
||||||
properties: {
|
|
||||||
action: { type: 'string', enum: ['search', 'add', 'replace', 'remove', 'remove_batch', 'read_all'], description: 'Which operation to perform. Note: "add" requires "type"+"content", "replace" requires "old_text"+"new_content", "remove" requires "old_text", "remove_batch" requires "old_texts" (array), "search" requires "query".' },
|
|
||||||
query: { type: 'string', description: '[search REQUIRED] Keywords to search for.' },
|
|
||||||
limit: { type: 'integer', description: '[search] Max results. Default: 8.' },
|
|
||||||
type: { type: 'string', enum: ['fact', 'preference', 'rule'], description: '[add REQUIRED] Memory type. fact=user info, preference=user preference, rule=behavior rule.' },
|
|
||||||
content: { type: 'string', description: '[add REQUIRED, replace] Memory text. Keep it concise (8-100 chars).' },
|
|
||||||
importance: { type: 'integer', description: '[add] Priority 1-10. Default: 5. Use 8+ for critical info.' },
|
|
||||||
tags: { type: 'array', items: { type: 'string' }, description: '[add] 2-5 keywords for search matching.' },
|
|
||||||
old_text: { type: 'string', description: '[replace, remove REQUIRED] Unique substring to identify the entry. Must be ≥5 chars. Use exact text from read_all for best results.' },
|
|
||||||
old_texts: { type: 'array', items: { type: 'string' }, description: '[remove_batch REQUIRED] Array of substrings to identify multiple entries for batch deletion. Each must be ≥5 chars.' },
|
|
||||||
new_content: { type: 'string', description: '[replace REQUIRED] New text to replace with.' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// ══════════════════════════════════════════════
|
|
||||||
// 会话管理工具
|
|
||||||
// ══════════════════════════════════════════════
|
|
||||||
{
|
|
||||||
type: 'function',
|
|
||||||
function: {
|
|
||||||
name: 'session_list',
|
|
||||||
description: 'List previous chat sessions with their titles and timestamps. Useful for referencing past conversations.',
|
|
||||||
parameters: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
limit: { type: 'integer', description: 'Max sessions to return. Default: 20.' },
|
|
||||||
search: { type: 'string', description: 'Filter sessions by title keyword.' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'function',
|
|
||||||
function: {
|
|
||||||
name: 'session_read',
|
|
||||||
description: 'Read the messages from a previous chat session. Use when the user references something from a past conversation.',
|
|
||||||
parameters: {
|
|
||||||
type: 'object',
|
|
||||||
required: ['session_id'],
|
|
||||||
properties: {
|
|
||||||
session_id: { type: 'string', description: 'The session ID to read.' },
|
|
||||||
max_messages: { type: 'integer', description: 'Max messages to return. Default: 50.' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// ══════════════════════════════════════════════
|
|
||||||
// v4.3 新增工具:子代理委派
|
|
||||||
// ══════════════════════════════════════════════
|
|
||||||
{
|
|
||||||
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 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'],
|
|
||||||
properties: {
|
|
||||||
task: { type: 'string', description: 'The task description for the sub-agent to execute.' },
|
|
||||||
context: { type: 'string', description: 'Optional additional context or reference data for the sub-agent.' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// ══════════════════════════════════════════════
|
|
||||||
// 新增工具:Plan Mode 执行追踪
|
|
||||||
// ══════════════════════════════════════════════
|
|
||||||
{
|
|
||||||
type: 'function',
|
|
||||||
function: {
|
|
||||||
name: 'plan_track',
|
|
||||||
description: 'Manage Plan Mode execution progress. Use this to mark plan steps as completed so you always know what remains. REQUIRED in Plan Mode — call plan_track after completing each step.',
|
|
||||||
parameters: {
|
|
||||||
type: 'object',
|
|
||||||
required: ['action'],
|
|
||||||
properties: {
|
|
||||||
action: { type: 'string', enum: ['status', 'mark_done', 'mark_undone', 'mark_all_done'], description: 'status=查看当前进度, mark_done=标记步骤完成, mark_undone=撤销步骤完成标记, mark_all_done=全部完成' },
|
|
||||||
step_index: { type: 'integer', description: 'Step number (1-indexed) to mark as done. Required for mark_done.' },
|
|
||||||
step_label: { type: 'string', description: 'Optional: description of what was completed, for logging.' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// ══════════════════════════════════════════════
|
|
||||||
// v5.0 新增工具:浏览器控制
|
|
||||||
// ══════════════════════════════════════════════
|
|
||||||
// v5.1 Browser 控制工具(增强版)
|
|
||||||
// ══════════════════════════════════════════════
|
|
||||||
{
|
|
||||||
type: 'function',
|
|
||||||
function: {
|
|
||||||
name: 'browser_open',
|
|
||||||
description: 'Open a URL in the agent browser. Waits for page load. Use wait_selector to wait for a specific element (useful for SPA pages). Returns page title and URL.',
|
|
||||||
parameters: {
|
|
||||||
type: 'object',
|
|
||||||
required: ['url'],
|
|
||||||
properties: {
|
|
||||||
url: { type: 'string', description: 'The URL to open (http/https).' },
|
|
||||||
wait_selector: { type: 'string', description: 'CSS selector to wait for after page load. Useful for SPA/dynamic pages. Default: none.' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'function',
|
|
||||||
function: {
|
|
||||||
name: 'browser_screenshot',
|
|
||||||
description: 'Take a screenshot of the current page. Supports viewport (default), full_page (entire scrollable page), or selector (specific element only). Returns base64 PNG.',
|
|
||||||
parameters: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
full_page: { type: 'boolean', description: 'Capture the entire scrollable page. Default: false.' },
|
|
||||||
selector: { type: 'string', description: 'CSS selector of element to capture. Overrides full_page.' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'function',
|
|
||||||
function: {
|
|
||||||
name: 'browser_evaluate',
|
|
||||||
description: 'Execute JavaScript in the browser page. Returns the result as JSON string. Use to read DOM, extract data, or interact programmatically.',
|
|
||||||
parameters: {
|
|
||||||
type: 'object',
|
|
||||||
required: ['js'],
|
|
||||||
properties: {
|
|
||||||
js: { type: 'string', description: 'JavaScript code to execute in the page context.' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'function',
|
|
||||||
function: {
|
|
||||||
name: 'browser_extract',
|
|
||||||
description: 'Extract text and links from the current page. Use selector to extract only from a specific element (e.g. "main", "#content", "article"). Without selector, extracts full page. Returns title, text, and up to 50 links.',
|
|
||||||
parameters: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
selector: { type: 'string', description: 'CSS selector to extract from. Default: entire body.' },
|
|
||||||
max_chars: { type: 'integer', description: 'Max characters to return. Default: 15000.' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'function',
|
|
||||||
function: {
|
|
||||||
name: 'browser_click',
|
|
||||||
description: 'Click an element by CSS selector. Use wait=true to wait up to 10s for the element to appear first (useful after page navigation). Scrolls element into view before clicking.',
|
|
||||||
parameters: {
|
|
||||||
type: 'object',
|
|
||||||
required: ['selector'],
|
|
||||||
properties: {
|
|
||||||
selector: { type: 'string', description: 'CSS selector for the element to click.' },
|
|
||||||
wait: { type: 'boolean', description: 'Wait for element to appear before clicking (max 10s). Default: false.' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'function',
|
|
||||||
function: {
|
|
||||||
name: 'browser_type',
|
|
||||||
description: 'Type text into an input field. Use clear=false to append instead of replacing. Use submit=true to press Enter or submit the parent form after typing.',
|
|
||||||
parameters: {
|
|
||||||
type: 'object',
|
|
||||||
required: ['selector', 'text'],
|
|
||||||
properties: {
|
|
||||||
selector: { type: 'string', description: 'CSS selector for the input element.' },
|
|
||||||
text: { type: 'string', description: 'Text to type into the input.' },
|
|
||||||
clear: { type: 'boolean', description: 'Clear existing text first. Default: true.' },
|
|
||||||
submit: { type: 'boolean', description: 'Submit the form or press Enter after typing. Default: false.' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'function',
|
|
||||||
function: {
|
|
||||||
name: 'browser_scroll',
|
|
||||||
description: 'Scroll the page. Use direction for up/down/top/bottom, or selector to scroll a specific element into view.',
|
|
||||||
parameters: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
direction: { type: 'string', enum: ['down', 'up', 'top', 'bottom'], description: 'Scroll direction. Default: down.' },
|
|
||||||
selector: { type: 'string', description: 'CSS selector to scroll into view (overrides direction).' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'function',
|
|
||||||
function: {
|
|
||||||
name: 'browser_wait',
|
|
||||||
description: 'Wait for a condition. Use selector to wait for an element to appear, or time_ms for a fixed delay. Default: 1 second.',
|
|
||||||
parameters: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
selector: { type: 'string', description: 'CSS selector to wait for. Returns found: true/false.' },
|
|
||||||
time_ms: { type: 'integer', description: 'Fixed wait time in milliseconds. Default: 1000.' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'function',
|
|
||||||
function: {
|
|
||||||
name: 'browser_close',
|
|
||||||
description: 'Close the agent browser and free resources.',
|
|
||||||
parameters: { type: 'object', properties: {} }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: 'function',
|
type: 'function',
|
||||||
function: {
|
function: {
|
||||||
@@ -914,38 +648,38 @@ const CORE_TOOLS = new Set([
|
|||||||
|
|
||||||
/** 工具关键词映射 — 用于语义匹配 */
|
/** 工具关键词映射 — 用于语义匹配 */
|
||||||
const TOOL_KEYWORDS: Record<string, string[]> = {
|
const TOOL_KEYWORDS: Record<string, string[]> = {
|
||||||
read_file: ['read', 'file', '读取', '文件', '看', '查看', '内容'],
|
read_file: ['read', 'file', '读取', '文件', '看', '查看', '内容', '打开文件', 'cat', 'less', 'head', 'tail'],
|
||||||
write_file: ['write', 'file', '写入', '保存', '创建文件', '输出'],
|
write_file: ['write', 'file', '写入', '保存', '创建文件', '输出', '写入文件', '写入内容', 'echo'],
|
||||||
list_directory: ['list', 'directory', '目录', '文件夹', '列'],
|
list_directory: ['list', 'directory', '目录', '文件夹', '列', 'ls', '列出', '查看目录'],
|
||||||
search_files: ['search', 'find', '搜索', '查找', 'grep', 'find'],
|
search_files: ['search', 'find', '搜索', '查找', 'grep', 'find', '正则', '通配符', 'rg', 'ag'],
|
||||||
create_directory: ['create', 'directory', 'mkdir', '创建目录', '新建'],
|
create_directory: ['create', 'directory', 'mkdir', '创建目录', '新建', '新建文件夹'],
|
||||||
delete_file: ['delete', 'remove', '删除', 'rm', '移除'],
|
delete_file: ['delete', 'remove', '删除', 'rm', '移除', '清空'],
|
||||||
run_command: ['run', 'command', 'shell', '执行', '命令', '终端'],
|
run_command: ['run', 'command', 'shell', '执行', '命令', '终端', 'cmd', 'powershell', 'bash', '脚本'],
|
||||||
move_file: ['move', 'rename', '移动', '重命名', 'mv'],
|
move_file: ['move', 'rename', '移动', '重命名', 'mv', '改名', '移动文件'],
|
||||||
copy_file: ['copy', '复制', 'cp'],
|
copy_file: ['copy', '复制', 'cp', '拷贝'],
|
||||||
web_fetch: ['fetch', 'url', '网页', '抓取', '获取'],
|
web_fetch: ['fetch', 'url', '网页', '抓取', '获取', '爬虫', '网页内容', '页面', '网址', '链接', '请求'],
|
||||||
web_search: ['search', 'web', '搜索', '联网', '查', 'google', 'bing'],
|
web_search: ['search', 'web', '搜索', '联网', '查', 'google', 'bing', '百度', '搜狗', '360', '查询', '在线搜索'],
|
||||||
edit_file: ['edit', 'replace', '编辑', '替换', '修改'],
|
edit_file: ['edit', 'replace', '编辑', '替换', '修改', 'sed', '补丁', 'patch', '修改文件'],
|
||||||
tree: ['tree', '结构', '树', '目录结构'],
|
tree: ['tree', '结构', '树', '目录结构', '层级', 'find'],
|
||||||
download_file: ['download', '下载'],
|
download_file: ['download', '下载', '保存到本地', '下载文件'],
|
||||||
read_multiple_files: ['read', 'multiple', '批量读取'],
|
read_multiple_files: ['read', 'multiple', '批量读取', '多个文件', '并行读取', '批量'],
|
||||||
git: ['git', 'commit', 'push', 'pull', 'branch', '仓库'],
|
git: ['git', 'commit', 'push', 'pull', 'branch', '仓库', '版本控制', 'checkout', 'merge', 'diff', 'log', 'stash', 'tag'],
|
||||||
compress: ['compress', 'zip', 'tar', '压缩', '解压', 'extract'],
|
compress: ['compress', 'zip', 'tar', '压缩', '解压', 'extract', '打包', '归档', '解压缩', 'gzip'],
|
||||||
memory: ['memory', '记忆', 'remember', 'save', 'rule', '规则'],
|
memory: ['memory', '记忆', 'remember', 'save', 'rule', '规则', '记住', '偏好', '事实', '遗忘', '回忆'],
|
||||||
session_list: ['session', 'history', '会话', '历史'],
|
session_list: ['session', 'history', '会话', '历史', '历史记录', '会话列表'],
|
||||||
session_read: ['session', 'read', '读取会话'],
|
session_read: ['session', 'read', '读取会话', '查看会话', '历史会话'],
|
||||||
spawn_task: ['sub', 'agent', 'delegate', '子代理', '委派'],
|
spawn_task: ['sub', 'agent', 'delegate', '子代理', '委派', '并行', '子任务', '委派任务'],
|
||||||
plan_track: ['plan', 'track', '计划', '进度'],
|
plan_track: ['plan', 'track', '计划', '进度', '步骤', '追踪', '计划追踪'],
|
||||||
browser_open: ['browser', 'open', '浏览器', '打开网页'],
|
browser_open: ['browser', 'open', '浏览器', '打开网页', '访问', '浏览', '打开网站', '访问网页'],
|
||||||
browser_screenshot: ['screenshot', '截图', '屏幕'],
|
browser_screenshot: ['screenshot', '截图', '屏幕', '截屏', '画面', '截取'],
|
||||||
browser_evaluate: ['evaluate', 'javascript', 'js', '执行JS'],
|
browser_evaluate: ['evaluate', 'javascript', 'js', '执行JS', '运行JS', '脚本执行', 'DOM'],
|
||||||
browser_extract: ['extract', '提取', '内容'],
|
browser_extract: ['extract', '提取', '内容', '读取网页', '获取内容', '网页文本'],
|
||||||
browser_click: ['click', '点击'],
|
browser_click: ['click', '点击', '按钮', '链接', '选择', '按下'],
|
||||||
browser_type: ['type', 'input', '输入'],
|
browser_type: ['type', 'input', '输入', '填写', '表单', '输入框'],
|
||||||
browser_scroll: ['scroll', '滚动'],
|
browser_scroll: ['scroll', '滚动', '翻页', '滑', '向下', '向上'],
|
||||||
browser_wait: ['wait', '等待'],
|
browser_wait: ['wait', '等待', '加载', '延时', '延迟'],
|
||||||
browser_close: ['close', '关闭浏览器'],
|
browser_close: ['close', '关闭浏览器', '关闭网页', '退出浏览器'],
|
||||||
calculator: ['calculate', 'math', '计算', '算'],
|
calculator: ['calculate', 'math', '计算', '算', '算术', '求值', '数学', '表达式'],
|
||||||
};
|
};
|
||||||
|
|
||||||
/** R55: 根据用户查询语义过滤工具定义,减少 token 占用 */
|
/** R55: 根据用户查询语义过滤工具定义,减少 token 占用 */
|
||||||
|
|||||||
Vendored
+1
-2
@@ -538,9 +538,8 @@ export interface DBAPI {
|
|||||||
getMessages: (sessionId: string) => Promise<MessageRow[]>;
|
getMessages: (sessionId: string) => Promise<MessageRow[]>;
|
||||||
saveSetting: (key: string, value: unknown) => Promise<{ success: boolean; error?: string }>;
|
saveSetting: (key: string, value: unknown) => Promise<{ success: boolean; error?: string }>;
|
||||||
getSetting: <T = unknown>(key: string, defaultValue?: T) => Promise<T>;
|
getSetting: <T = unknown>(key: string, defaultValue?: T) => Promise<T>;
|
||||||
saveToolCall: (tc: unknown) => Promise<{ success: boolean; id?: string; error?: string }>;
|
|
||||||
getToolCalls: (sessionId: string) => Promise<unknown[]>;
|
|
||||||
saveTrace: (trace: unknown) => Promise<{ success: boolean; id?: string; error?: string }>;
|
saveTrace: (trace: unknown) => Promise<{ success: boolean; id?: string; error?: string }>;
|
||||||
|
saveTracesBatch: (traces: unknown[]) => Promise<{ success: boolean; count?: number; error?: string }>;
|
||||||
getTraces: (sessionId: string) => Promise<TraceEntry[]>;
|
getTraces: (sessionId: string) => Promise<TraceEntry[]>;
|
||||||
exportSessions: () => Promise<unknown>;
|
exportSessions: () => Promise<unknown>;
|
||||||
importSessions: (data: unknown) => Promise<{ imported: number; skipped: number }>;
|
importSessions: (data: unknown) => Promise<{ imported: number; skipped: number }>;
|
||||||
|
|||||||
Reference in New Issue
Block a user