feat: v0.10.2 - 可配置超时设置 + 默认值提升

- HTTP超时: 15s→30s(可配5s~300s), MCP超时: 30s→60s(可配10s~300s), 流式超时: 180s→300s(可配0~600s)
- 设置面板新增超时配置区域(HTTP/流式/MCP),留空使用默认值
- 主进程HTTP_TIMEOUT/MCP_TIMEOUT改为可配置变量,通过IPC setTimeouts通道接收
- 帮助面板/README/release-notes更新四引擎搜索和超时描述
- 版本号同步更新至v0.10.2(16处)
This commit is contained in:
thzxx
2026-06-10 09:39:59 +08:00
parent 64264cc0f8
commit 6390457312
17 changed files with 157 additions and 27 deletions
+3 -3
View File
@@ -14,7 +14,7 @@
</p> </p>
<p align="center"> <p align="center">
<img src="https://img.shields.io/badge/version-v0.10.1-E8734A?style=flat-square" alt="version"> <img src="https://img.shields.io/badge/version-v0.10.2-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">
@@ -231,7 +231,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.10.1.exe` 产出:`release/Metona Ollama Setup v0.10.2.exe`
## 🛠️ 常用命令 ## 🛠️ 常用命令
@@ -454,7 +454,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.10.1.exe` Output: `release/Metona Ollama Setup v0.10.2.exe`
## 🛠️ Common Commands ## 🛠️ Common Commands
+3 -3
View File
@@ -91,7 +91,7 @@ npm run dist # NSIS 安装包
| 文件 | 说明 | | 文件 | 说明 |
|------|------| |------|------|
| `release/Metona Ollama Setup v0.10.1.exe` | NSIS 安装包(可选目录、创建快捷方式) | | `release/Metona Ollama Setup v0.10.2.exe` | NSIS 安装包(可选目录、创建快捷方式) |
> 仅提供 NSIS 安装包。 > 仅提供 NSIS 安装包。
@@ -103,7 +103,7 @@ npm run dist # NSIS 安装包
TOKEN="your_access_token" TOKEN="your_access_token"
curl -X POST "https://gitee.com/api/v5/repos/thzxx/metona-ollama-desktop/releases?access_token=$TOKEN" \ curl -X POST "https://gitee.com/api/v5/repos/thzxx/metona-ollama-desktop/releases?access_token=$TOKEN" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"tag_name":"v0.10.1","name":"v0.10.1","body":"Release notes","target_commitish":"master"}' -d '{"tag_name":"v0.10.2","name":"v0.10.2","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" TOKEN="your_access_token"
RELEASE_ID="<release_id>" RELEASE_ID="<release_id>"
curl -X POST "https://gitee.com/api/v5/repos/thzxx/metona-ollama-desktop/releases/$RELEASE_ID/attach_files?access_token=$TOKEN" \ 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.10.1.exe" -H "Content-Type: multipart/form-data" -F "file=@release/Metona Ollama Setup v0.10.2.exe"
``` ```
## 故障排查 ## 故障排查
+1 -1
View File
@@ -1,6 +1,6 @@
# Metona Ollama Desktop — 开发规范 # Metona Ollama Desktop — 开发规范
> 版本: v0.10.1 | 更新: 2026-06-05 | 维护: 项目团队 > 版本: v0.10.2 | 更新: 2026-06-05 | 维护: 项目团队
--- ---
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "metona-ollama-desktop", "name": "metona-ollama-desktop",
"version": "0.10.1", "version": "0.10.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "metona-ollama-desktop", "name": "metona-ollama-desktop",
"version": "0.10.1", "version": "0.10.2",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"sql.js": "^1.11.0" "sql.js": "^1.11.0"
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "metona-ollama-desktop", "name": "metona-ollama-desktop",
"version": "0.10.1", "version": "0.10.2",
"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",
@@ -48,7 +48,7 @@
"requestedExecutionLevel": "asInvoker" "requestedExecutionLevel": "asInvoker"
}, },
"nsis": { "nsis": {
"artifactName": "Metona Ollama Setup v0.10.1.${ext}", "artifactName": "Metona Ollama Setup v0.10.2.${ext}",
"oneClick": false, "oneClick": false,
"allowToChangeInstallationDirectory": true, "allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true, "createDesktopShortcut": true,
+5 -5
View File
@@ -4,7 +4,7 @@
## 项目简介 ## 项目简介
Metona Ollama Desktop 是一个基于 **Ollama** 的本地 AI 桌面客户端,采用 TypeScript + Electron 构建。所有 AI 推理均在本地完成,数据不离开本机。支持 ReAct Agent Loop、38 个内置工具、智能记忆系统、MCP 协议扩展、引擎联网搜索、浏览器控制等能力。 Metona Ollama Desktop 是一个基于 **Ollama** 的本地 AI 桌面客户端,采用 TypeScript + Electron 构建。所有 AI 推理均在本地完成,数据不离开本机。支持 ReAct Agent Loop、38 个内置工具、智能记忆系统、MCP 协议扩展、引擎联网搜索、浏览器控制等能力。
--- ---
@@ -24,7 +24,7 @@ Metona Ollama Desktop 是一个基于 **Ollama** 的本地 AI 桌面客户端,
|------|------|------| |------|------|------|
| 文件系统 | 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 | | 文件系统 | 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 |
| 命令执行 | 1 | run_command(实时流式输出,工作空间终端集成) | | 命令执行 | 1 | run_command(实时流式输出,工作空间终端集成) |
| 联网搜索 | 2 | web_searchBing + 百度 + Google 引擎), web_fetch | | 联网搜索 | 2 | web_searchBing + 百度 + DuckDuckGo + Google 引擎), web_fetch |
| 浏览器控制 | 8 | browser_open, browser_screenshot, browser_evaluate, browser_extract, browser_click, browser_type, browser_scroll, browser_close | | 浏览器控制 | 8 | browser_open, browser_screenshot, browser_evaluate, browser_extract, browser_click, browser_type, browser_scroll, browser_close |
| Git | 1 | gitinit/clone/add/commit/push/pull/diff/log/status/branch/checkout/merge/stash/reset/tag/remote | | Git | 1 | gitinit/clone/add/commit/push/pull/diff/log/status/branch/checkout/merge/stash/reset/tag/remote |
| 记忆管理 | 4 | memory_search, memory_add, memory_replace, memory_remove | | 记忆管理 | 4 | memory_search, memory_add, memory_replace, memory_remove |
@@ -43,10 +43,10 @@ Metona Ollama Desktop 是一个基于 **Ollama** 的本地 AI 桌面客户端,
- JSON-RPC 2.0 over stdio 标准协议 - JSON-RPC 2.0 over stdio 标准协议
- 动态工具发现与执行 - 动态工具发现与执行
- Shadowing 防护(防止 MCP 工具覆盖内置工具名) - Shadowing 防护(防止 MCP 工具覆盖内置工具名)
- 30 秒请求超时保护 - 60 秒请求超时保护
### 🔍 引擎联网搜索 ### 🔍 引擎联网搜索
- Bing + 百度 + Google 引擎聚合 - Bing + 百度 + DuckDuckGo + Google 引擎聚合
- web_search 返回标题、URL、摘要(默认 15 条) - web_search 返回标题、URL、摘要(默认 15 条)
- web_fetch 支持网页内容抓取(默认不截断) - web_fetch 支持网页内容抓取(默认不截断)
- 工具链规则:搜索后自动引导抓取详情 - 工具链规则:搜索后自动引导抓取详情
+12 -1
View File
@@ -49,9 +49,10 @@ import {
handleCompress handleCompress
} from './tool-handlers.js'; } from './tool-handlers.js';
import { browserOpen, browserScreenshot, browserEvaluate, browserExtract, browserClick, browserType, browserScroll, browserClose } from './browser.js'; import { browserOpen, browserScreenshot, browserEvaluate, browserExtract, browserClick, browserType, browserScroll, browserClose } from './browser.js';
import { startServer, stopServer, stopAllServers, callTool, getAllTools, getServerStatuses, refreshTools } from './mcp-manager.js'; import { startServer, stopServer, stopAllServers, callTool, getAllTools, getServerStatuses, refreshTools, setMCPTimeout } from './mcp-manager.js';
import { getAllowedDirs, getBlockedDirs, setAllowedDirs } from './tool-security.js'; import { getAllowedDirs, getBlockedDirs, setAllowedDirs } from './tool-security.js';
import { startProcess, killProcess, getWorkspaceDir, setWorkspaceDir, listWorkspaceDir } from './workspace.js'; import { startProcess, killProcess, getWorkspaceDir, setWorkspaceDir, listWorkspaceDir } from './workspace.js';
import { setHTTPTimeout } from './tool-handlers-system.js';
/** 工具结果摘要(用于日志面板) */ /** 工具结果摘要(用于日志面板) */
function summarizeResult(toolName: string, result: Record<string, unknown>): string { function summarizeResult(toolName: string, result: Record<string, unknown>): string {
@@ -222,6 +223,16 @@ export async function setupIPC(): Promise<void> {
setAllowedDirs(dirs); setAllowedDirs(dirs);
}); });
ipcMain.handle('tool:setTimeouts', (_, timeouts: { http?: number; mcp?: number }) => {
if (typeof timeouts.http === 'number' && timeouts.http > 0) {
setHTTPTimeout(timeouts.http);
}
if (typeof timeouts.mcp === 'number' && timeouts.mcp > 0) {
setMCPTimeout(timeouts.mcp);
}
return { success: true };
});
// ── Workspace IPC ── // ── Workspace IPC ──
// 获取工作空间目录 // 获取工作空间目录
+7 -1
View File
@@ -52,7 +52,13 @@ interface MCPServerInstance {
// ─── 状态 ─── // ─── 状态 ───
const servers = new Map<string, MCPServerInstance>(); const servers = new Map<string, MCPServerInstance>();
const MCP_TIMEOUT = 30000; // 30 秒超时 /** MCP 请求超时(毫秒),默认 60s,可通过 IPC 设置 */
let MCP_TIMEOUT = 60_000;
/** 设置 MCP 请求超时(毫秒) */
export function setMCPTimeout(ms: number): void {
MCP_TIMEOUT = Math.max(10_000, Math.min(300_000, ms)); // 10s ~ 300s
}
// ─── JSON-RPC 通信 ─── // ─── JSON-RPC 通信 ───
+1 -1
View File
@@ -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.10.1', message: 'Metona Ollama Desktop v0.10.2',
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()
}); });
+2 -1
View File
@@ -19,7 +19,8 @@ contextBridge.exposeInMainWorld('metonaDesktop', {
tool: { tool: {
execute: (toolName: string, args: Record<string, unknown>) => ipcRenderer.invoke('tool:execute', toolName, args), execute: (toolName: string, args: Record<string, unknown>) => ipcRenderer.invoke('tool:execute', toolName, args),
getConfig: () => ipcRenderer.invoke('tool:getConfig'), getConfig: () => ipcRenderer.invoke('tool:getConfig'),
setAllowedDirs: (dirs: string[]) => ipcRenderer.invoke('tool:setAllowedDirs', dirs) setAllowedDirs: (dirs: string[]) => ipcRenderer.invoke('tool:setAllowedDirs', dirs),
setTimeouts: (timeouts: { http?: number; mcp?: number }) => ipcRenderer.invoke('tool:setTimeouts', timeouts)
}, },
notify: (title: string, body: string) => ipcRenderer.invoke('notify', title, body), notify: (title: string, body: string) => ipcRenderer.invoke('notify', title, body),
window: { window: {
+7 -2
View File
@@ -118,8 +118,13 @@ export function killToolProcess(): boolean {
/** HTTP 请求超时(毫秒) */ /** HTTP 请求超时(毫秒),默认 30s,可通过 IPC 设置 */
const HTTP_TIMEOUT = 15_000; let HTTP_TIMEOUT = 30_000;
/** 设置 HTTP 请求超时(毫秒) */
export function setHTTPTimeout(ms: number): void {
HTTP_TIMEOUT = Math.max(5000, Math.min(300_000, ms)); // 5s ~ 300s
}
// ── LRU 搜索缓存 ────────────────────────────────── // ── LRU 搜索缓存 ──────────────────────────────────
interface CacheEntry { results: Array<{ title: string; url: string; snippet: string; engine: string; reachable?: boolean }>; time: number; } interface CacheEntry { results: Array<{ title: string; url: string; snippet: string; engine: string; reachable?: boolean }>; time: number; }
+62
View File
@@ -64,6 +64,49 @@ export function initSettingsModal(): void {
}, 500); }, 500);
document.querySelector('#inputMaxTurns')!.addEventListener('input', saveMaxTurns); document.querySelector('#inputMaxTurns')!.addEventListener('input', saveMaxTurns);
// ── v5.2 超时设置 ──
const inputHttpTimeout = document.querySelector('#inputHttpTimeout') as HTMLInputElement;
const inputStreamTimeout = document.querySelector('#inputStreamTimeout') as HTMLInputElement;
const inputMCPTimeout = document.querySelector('#inputMCPTimeout') as HTMLInputElement;
const applyTimeouts = async () => {
const db = state.get<ChatDB | null>(KEYS.DB);
const bridge = window.metonaDesktop;
// HTTP 超时(秒→毫秒)
const httpVal = inputHttpTimeout.value.trim();
const httpSec = httpVal ? parseInt(httpVal) : 0;
if (db) await db.saveSetting('httpTimeout', httpSec > 0 ? httpSec : 0);
if (bridge?.tool?.setTimeouts) {
await bridge.tool.setTimeouts({ http: httpSec > 0 ? httpSec * 1000 : 30_000 });
}
logSetting('HTTP 超时', httpSec > 0 ? `${httpSec}s` : '默认(30s)');
// 流式超时(秒→毫秒)
const streamVal = inputStreamTimeout.value.trim();
const streamSec = streamVal ? parseInt(streamVal) : 0;
state.set('streamTimeout', streamSec > 0 ? streamSec * 1000 : 300_000);
if (db) await db.saveSetting('streamTimeout', streamSec > 0 ? streamSec : 0);
logSetting('流式超时', streamSec > 0 ? `${streamSec}s` : '默认(300s)');
// MCP 超时(秒→毫秒)
const mcpVal = inputMCPTimeout.value.trim();
const mcpSec = mcpVal ? parseInt(mcpVal) : 0;
if (db) await db.saveSetting('mcpTimeout', mcpSec > 0 ? mcpSec : 0);
if (bridge?.tool?.setTimeouts) {
await bridge.tool.setTimeouts({ mcp: mcpSec > 0 ? mcpSec * 1000 : 60_000 });
}
logSetting('MCP 超时', mcpSec > 0 ? `${mcpSec}s` : '默认(60s)');
};
const saveHttpTimeout = debounce(applyTimeouts, 500);
const saveStreamTimeout = debounce(applyTimeouts, 500);
const saveMCPTimeout = debounce(applyTimeouts, 500);
inputHttpTimeout.addEventListener('input', saveHttpTimeout);
inputStreamTimeout.addEventListener('input', saveStreamTimeout);
inputMCPTimeout.addEventListener('input', saveMCPTimeout);
// ── v5.0 MCP 服务器管理 ── // ── v5.0 MCP 服务器管理 ──
async function renderMCPServerList(): Promise<void> { async function renderMCPServerList(): Promise<void> {
const { getMCPServers, getMCPServerStatuses, toggleMCPServer, removeMCPServer } = await import('../services/mcp-client.js'); const { getMCPServers, getMCPServerStatuses, toggleMCPServer, removeMCPServer } = await import('../services/mcp-client.js');
@@ -259,12 +302,31 @@ export function openSettingsModal(): void {
populateMemoryEmbedModels(); populateMemoryEmbedModels();
populateSubAgentModels(); populateSubAgentModels();
updateMemoryVectorStatus(); updateMemoryVectorStatus();
loadTimeoutSettings();
} }
export function closeSettingsModal(): void { export function closeSettingsModal(): void {
settingsModalEl.style.display = 'none'; settingsModalEl.style.display = 'none';
} }
/** 加载已保存的超时设置到输入框 */
async function loadTimeoutSettings(): Promise<void> {
const db = state.get<ChatDB | null>(KEYS.DB);
if (!db) return;
const httpTimeout = await db.getSetting<number>('httpTimeout', 0);
const streamTimeout = await db.getSetting<number>('streamTimeout', 0);
const mcpTimeout = await db.getSetting<number>('mcpTimeout', 0);
const inputHttp = document.querySelector('#inputHttpTimeout') as HTMLInputElement;
const inputStream = document.querySelector('#inputStreamTimeout') as HTMLInputElement;
const inputMCP = document.querySelector('#inputMCPTimeout') as HTMLInputElement;
if (inputHttp) inputHttp.value = httpTimeout > 0 ? String(httpTimeout) : '';
if (inputStream) inputStream.value = streamTimeout > 0 ? String(streamTimeout) : '';
if (inputMCP) inputMCP.value = mcpTimeout > 0 ? String(mcpTimeout) : '';
}
async function exportAllSessions(): Promise<void> { async function exportAllSessions(): Promise<void> {
const db = state.get<ChatDB | null>(KEYS.DB); const db = state.get<ChatDB | null>(KEYS.DB);
if (!db) return; if (!db) return;
+23 -2
View File
@@ -28,7 +28,7 @@
<div class="header-left"> <div class="header-left">
<span class="logo">🦙</span> <span class="logo">🦙</span>
<span class="app-title">Metona Ollama</span> <span class="app-title">Metona Ollama</span>
<span class="app-version">v0.10.1</span> <span class="app-version">v0.10.2</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"/>
@@ -287,6 +287,27 @@
</div> </div>
<p class="text-muted" style="margin-top:4px;font-size:11px;">Agent Loop 最大工具调用轮数。默认 85 轮,复杂任务可能需要更多轮次。</p> <p class="text-muted" style="margin-top:4px;font-size:11px;">Agent Loop 最大工具调用轮数。默认 85 轮,复杂任务可能需要更多轮次。</p>
</div> </div>
<div class="setting-group">
<label class="setting-label">⏱️ 超时设置(Timeout</label>
<p class="text-muted" style="font-size:11px;margin-bottom:10px;">调整各通信层超时时长。留空则使用默认值(HTTP:30s / 流式:300s / MCP:60s)。设为 0 可禁用超时。</p>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
<div>
<label style="font-size:12px;color:var(--text-secondary);" for="inputHttpTimeout">🌐 HTTP 请求超时(秒)</label>
<input class="setting-input" id="inputHttpTimeout" type="number" min="0" max="300" step="5" placeholder="30" style="margin-bottom:0;">
<p class="text-muted" style="font-size:10px;">web_search / web_fetch 等 HTTP 请求的超时</p>
</div>
<div>
<label style="font-size:12px;color:var(--text-secondary);" for="inputStreamTimeout">📡 流式调用超时(秒)</label>
<input class="setting-input" id="inputStreamTimeout" type="number" min="0" max="600" step="10" placeholder="300" style="margin-bottom:0;">
<p class="text-muted" style="font-size:10px;">Ollama 单轮流式响应的最大等待时间</p>
</div>
<div style="grid-column:1/-1;">
<label style="font-size:12px;color:var(--text-secondary);" for="inputMCPTimeout">🔌 MCP 请求超时(秒)</label>
<input class="setting-input" id="inputMCPTimeout" type="number" min="0" max="300" step="10" placeholder="60" style="margin-bottom:0;">
<p class="text-muted" style="font-size:10px;">MCP Server JSON-RPC 请求的超时(需重启 MCP 服务器生效)</p>
</div>
</div>
</div>
<div class="setting-group"> <div class="setting-group">
<label class="setting-label">运行中的模型</label> <label class="setting-label">运行中的模型</label>
<div class="running-models" id="runningModels"> <div class="running-models" id="runningModels">
@@ -396,7 +417,7 @@
<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>💬 聊天功能</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>38 个工具</strong>,分为 8 类:<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_search4引擎并行:Bing+百度+DuckDuckGo+Google,结果缓存5分钟,URL可达性预检)/ web_fetch(自动重试+移动端UA切换+SPA页面自动升级到浏览器渲染)</li><li><strong>Git</strong>(1 个):git16 个子操作,push/pull/clone 内置60-120s超时保护)</li><li><strong>浏览器控制</strong>8 个):browser_open / browser_screenshot / browser_evaluate / browser_extract / browser_click / browser_type / browser_scroll / 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_multiple_files 支持<strong>并行读取</strong>diff_files 优先调用系统 diff/git diff 命令</li><li>search_files 支持<strong>正则表达式搜索</strong>use_regex=true</li><li>list_directory 支持 <strong>offset 分页</strong>,大目录可分页读取</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><li>⚠️ 需要模型支持 Tool Calling(推荐 Qwen3、Llama 3.1+、Mistral</li></ul></div> <div class="help-section"><h4>🔧 Tool Calling(始终开启)</h4><ul><li>所有消息均通过 <strong>Agent Loop 自主调用本地工具</strong>,像一个本地 Agent,无普通聊天模式</li><li><strong>38 个工具</strong>,分为 8 类:<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_search4引擎并行:Bing+百度+DuckDuckGo+Google,结果缓存5分钟,URL可达性预检)/ web_fetch(自动重试+移动端UA切换+SPA页面自动升级到浏览器渲染)</li><li><strong>Git</strong>(1 个):git16 个子操作,push/pull/clone 内置60-120s超时保护)</li><li><strong>浏览器控制</strong>8 个):browser_open / browser_screenshot / browser_evaluate / browser_extract / browser_click / browser_type / browser_scroll / 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_multiple_files 支持<strong>并行读取</strong>diff_files 优先调用系统 diff/git diff 命令</li><li>search_files 支持<strong>正则表达式搜索</strong>use_regex=true</li><li>list_directory 支持 <strong>offset 分页</strong>,大目录可分页读取</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><li>⚠️ 需要模型支持 Tool Calling(推荐 Qwen3、Llama 3.1+、Mistral</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 记忆系统</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.1 增强</h4><ul><li><strong>智能上下文压缩</strong>:滑动窗口 + LLM结构化JSON摘要,超过50%上下文窗口自动触发</li><li><strong>流式调用超时保护</strong>:可配置超时(默认180s),Ollama 卡死不再永久阻塞</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>🤖 Agent Loop v0.10.2 增强</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> <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>
<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>删除工作空间中的文件即可恢复为内置默认版本</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>删除工作空间中的文件即可恢复为内置默认版本</li></ul></div>
<div class="help-section"><h4>🎯 技能自动生成</h4><ul><li>AI 完成包含 <strong>2+ 工具调用</strong>的复杂任务后,自动从执行轨迹中提取可复用技能</li><li><strong>语义匹配</strong>:配置嵌入模型后,根据用户消息的语义自动匹配最相关技能</li><li><strong>参数自优化</strong>:同一技能链多次执行后,自动合并高频参数提示,越用越精准</li><li><strong>技能衰减</strong>:30 天未使用的技能自动降权,90 天几乎忽略</li><li><strong>链合并</strong>:相同前缀(前 2 步相同)的多个技能自动合并为抽象技能</li><li>AI 可通过 <code>skill_list</code>(渐进式摘要)和 <code>skill_view</code>(完整工具链)查看技能</li><li>技能存储在本地 SQLite,不会上传到任何服务器</li></ul></div> <div class="help-section"><h4>🎯 技能自动生成</h4><ul><li>AI 完成包含 <strong>2+ 工具调用</strong>的复杂任务后,自动从执行轨迹中提取可复用技能</li><li><strong>语义匹配</strong>:配置嵌入模型后,根据用户消息的语义自动匹配最相关技能</li><li><strong>参数自优化</strong>:同一技能链多次执行后,自动合并高频参数提示,越用越精准</li><li><strong>技能衰减</strong>:30 天未使用的技能自动降权,90 天几乎忽略</li><li><strong>链合并</strong>:相同前缀(前 2 步相同)的多个技能自动合并为抽象技能</li><li>AI 可通过 <code>skill_list</code>(渐进式摘要)和 <code>skill_view</code>(完整工具链)查看技能</li><li>技能存储在本地 SQLite,不会上传到任何服务器</li></ul></div>
+24
View File
@@ -362,6 +362,30 @@ async function init(): Promise<void> {
state.set('maxTurns', maxTurns); state.set('maxTurns', maxTurns);
(document.querySelector('#inputMaxTurns') as HTMLInputElement).value = String(maxTurns); (document.querySelector('#inputMaxTurns') as HTMLInputElement).value = String(maxTurns);
// ── v5.2 超时设置 ──
const httpTimeout = await db.getSetting<number>('httpTimeout', 0);
const streamTimeout = await db.getSetting<number>('streamTimeout', 0);
const mcpTimeout = await db.getSetting<number>('mcpTimeout', 0);
// 应用流式超时到 stateagent-engine 会读取)
state.set('streamTimeout', streamTimeout > 0 ? streamTimeout * 1000 : 300_000);
// 应用 HTTP 和 MCP 超时到主进程
const bridge = window.metonaDesktop;
if (bridge?.tool?.setTimeouts) {
await bridge.tool.setTimeouts({
http: httpTimeout > 0 ? httpTimeout * 1000 : 30_000,
mcp: mcpTimeout > 0 ? mcpTimeout * 1000 : 60_000,
});
}
// 恢复输入框显示值
if (document.querySelector('#inputHttpTimeout')) {
(document.querySelector('#inputHttpTimeout') as HTMLInputElement).value = httpTimeout > 0 ? String(httpTimeout) : '';
(document.querySelector('#inputStreamTimeout') as HTMLInputElement).value = streamTimeout > 0 ? String(streamTimeout) : '';
(document.querySelector('#inputMCPTimeout') as HTMLInputElement).value = mcpTimeout > 0 ? String(mcpTimeout) : '';
}
// ── Tool Calling 设置 ── // ── Tool Calling 设置 ──
const toolCallingEnabled = await db.getSetting('toolCallingEnabled', true); const toolCallingEnabled = await db.getSetting('toolCallingEnabled', true);
const runCommandMode = await db.getSetting('runCommandMode', 'confirm') as 'auto' | 'confirm' | 'disabled'; const runCommandMode = await db.getSetting('runCommandMode', 'confirm') as 'auto' | 'confirm' | 'disabled';
+1 -1
View File
@@ -6,7 +6,7 @@
- **名称**: Metona - **名称**: Metona
- **角色**: Metona Ollama 桌面 AI 助手 - **角色**: Metona Ollama 桌面 AI 助手
- **核心能力**: ReAct Agent Loop v0.10.1 + 38 个增强工具 + 记忆系统 + 技能自动生成 + 智能上下文压缩 - **核心能力**: ReAct Agent Loop v0.10.2 + 38 个增强工具 + 记忆系统 + 技能自动生成 + 智能上下文压缩
## 性格与语气 ## 性格与语气
+1 -1
View File
@@ -646,7 +646,7 @@ export async function runAgentLoop(
state.set(KEYS.ABORT_CONTROLLER, abortController); state.set(KEYS.ABORT_CONTROLLER, abortController);
// 流式调用超时保护(可配置,0 = 禁用超时) // 流式调用超时保护(可配置,0 = 禁用超时)
const STREAM_TIMEOUT_MS = state.get<number>('streamTimeout', 180_000); // 默认 180s const STREAM_TIMEOUT_MS = state.get<number>('streamTimeout', 300_000); // 默认 300s(可在设置中配置,0=禁用)
let streamTimer: ReturnType<typeof setTimeout> | null = null; let streamTimer: ReturnType<typeof setTimeout> | null = null;
if (STREAM_TIMEOUT_MS > 0) { if (STREAM_TIMEOUT_MS > 0) {
streamTimer = setTimeout(() => { streamTimer = setTimeout(() => {
+1 -1
View File
@@ -1,5 +1,5 @@
/** /**
* SubAgent - (v0.10.1 ) * SubAgent - (v0.10.2 )
* // * //
* Agent spawn_task * Agent spawn_task
*/ */