From 94e0a36981da750b3be2a54894410a1dcc5f8bc2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=B4=AB=E5=BD=B1233?=
Date: Thu, 16 Jul 2026 17:47:02 +0800
Subject: [PATCH] =?UTF-8?q?v0.16.12:=20=E5=88=A0=E9=99=A4=208=20=E4=B8=AA?=
=?UTF-8?q?=E9=9D=9E=E5=BF=85=E8=A6=81=E5=B7=A5=E5=85=B7=20=E2=80=94=20get?=
=?UTF-8?q?=5Ffile=5Finfo/diff=5Ffiles/replace=5Fin=5Ffiles/random/uuid/js?=
=?UTF-8?q?on=5Fformat/hash/datetime?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 38 +-
package-lock.json | 4 +-
package.json | 2 +-
src/main/ipc.ts | 26 +-
src/main/menu.ts | 2 +-
src/main/tool-handlers-fs.ts | 203 -----------
src/main/tool-handlers-system.ts | 203 -----------
src/main/tool-handlers.ts | 12 +-
src/renderer/components/tools-modal.ts | 2 +-
src/renderer/components/workspace-panel.ts | 8 +-
src/renderer/index.html | 36 +-
src/renderer/main.ts | 2 +-
src/renderer/services/agent-engine.ts | 56 +--
src/renderer/services/context-manager.ts | 4 -
src/renderer/services/tool-registry.ts | 396 ++++++++++++++-------
15 files changed, 301 insertions(+), 693 deletions(-)
diff --git a/README.md b/README.md
index 9645f91..350ee38 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
-
+
@@ -37,7 +37,7 @@
| 🤖 | **ReAct Agent Loop** | 始终开启的唯一对话模式。8 状态机(INIT→THINKING→PARSING→EXECUTING→OBSERVING→REFLECTING→COMPRESSING→TERMINATED),最大 85 轮(可配置),智能重试(永久错误立即返回、瞬态错误指数退避),工具去重,智能路径依赖串行化,看门狗超时保护 |
| 🛡️ | **5 层抗幻觉系统** | 提示词加固 → 任务感知 → 中途检测(中英双语规则覆盖全部工具类别)→ 进度锚点 → 完成闸门(6 项检查,幻觉/注入 → 阻断,质量/效率 → 咨询) |
| 📋 | **Plan Mode** | 开关切换,AI 首先生成执行计划(Markdown 渲染确认弹窗),批准后按步骤追踪执行,plan_track 工具标记完成状态,**支持断点续传**(中止后可恢复未完成计划) |
-| 🔧 | **40 个内置工具** | 文件系统(16个) · 命令执行 · 联网搜索 · 浏览器控制(9个) · Git · 记忆 · 会话 · 子代理 · 系统工具 · Plan Mode 追踪 |
+| 🔧 | **32 个内置工具** | 文件系统(13个) · 命令执行 · 联网搜索 · 浏览器控制(9个) · Git · 记忆 · 会话 · 子代理 · 系统工具 · Plan Mode 追踪 |
| 🧠 | **智能记忆系统** | 三类记忆(fact / preference / rule),存储于工作空间 MEMORY.md 文件,受路径保护仅 memory 工具可访问,写入前安全扫描,容量 500 条,对话结束自动提取,严格格式校验 |
| 📋 | **自定义文件** | SOUL.md(人格,不可压缩)+ AGENT.md(行为准则,内置 fallback)+ USER.md(用户画像,仅工作空间读取,不存在则不注入) |
| 🌐 | **MCP 协议扩展** | JSON-RPC 2.0 over stdio,动态工具发现,Shadowing 防护 |
@@ -59,7 +59,7 @@
## 🔧 工具清单
-📁 文件系统(16 个)
+📁 文件系统(13 个)
| 工具 | 功能 |
|------|------|
@@ -72,11 +72,8 @@
| `move_file` | 移动/重命名文件 |
| `copy_file` | 复制文件 |
| `edit_file` | 编辑文件(支持正则替换) |
-| `get_file_info` | 获取文件元信息 |
| `tree` | 目录树结构(默认5层深度) |
| `download_file` | 下载文件 |
-| `diff_files` | 文件差异对比 |
-| `replace_in_files` | 批量替换 |
| `read_multiple_files` | 批量读取(50文件/10KB) |
| `compress` | 压缩文件/目录 |
@@ -150,16 +147,11 @@
-🕐 系统工具(6 个)
+🕐 系统工具(1 个)
| 工具 | 功能 |
|------|------|
-| `datetime` | 获取系统精确时间(ISO/Unix/日期/时间/完整) |
| `calculator` | 安全数学计算(+ - * / ** % (),递归下降解析器) |
-| `random` | 随机生成(整数/浮点/抽取/随机字符串) |
-| `uuid` | 生成 UUID v4 唯一标识符 |
-| `json_format` | JSON 格式化 + 语法验证 + 键排序 |
-| `hash` | 哈希计算(MD5/SHA1/SHA256/SHA384/SHA512) |
@@ -185,7 +177,7 @@
↓
Ollama API (流式响应,num_ctx 用户可配 128K/256K/512K/1M)
↓
- Tool Registry (40 内置 + MCP 动态 + Plan Mode plan_track)
+ Tool Registry (32 内置 + MCP 动态 + Plan Mode plan_track)
↓
Hook 系统 (pre/post tool/iteration/completion) → 观察结果 → 反思 → 循环 / 最终回答
```
@@ -253,7 +245,7 @@ npm start
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist
```
-产出:`release/Metona Ollama Setup v0.16.11.exe`
+产出:`release/Metona Ollama Setup v0.16.12.exe`
## 🛠️ 常用命令
@@ -286,7 +278,7 @@ npm run dist # 构建 Windows 安装包
| 🤖 | **ReAct Agent Loop** | Always-on, only chat mode. 8-state machine, up to 85 iterations (configurable), smart retry (permanent errors return immediately, transient errors use exponential backoff), tool dedup, path-aware dependency serialization, watchdog + tiered timeout protection |
| 🛡️ | **5-Layer Anti-Hallucination** | Prompt hardening → task awareness → mid-task detection (bilingual CN/EN rules covering all tool categories) → progress anchors → completion gate (6 checks: hallucination/injection → block, quality/efficiency → advisory) |
| 📋 | **Plan Mode** | Toggle switch. AI first generates an execution plan (Markdown-rendered confirmation dialog), then tracks step-by-step execution, **supports resume** (interrupted plans restore on next session) |
-| 🔧 | **40 Built-in Tools** | File system · Command · Web search · Browser · Git · Memory · Sessions · Sub-agent · System · Plan Mode tracking |
+| 🔧 | **32 Built-in Tools** | File system · Command · Web search · Browser · Git · Memory · Sessions · Sub-agent · System · Plan Mode tracking |
| 🧠 | **Smart Memory System** | Three types (fact / preference / rule), stored in workspace MEMORY.md file, path-protected — only the memory tool can access it, pre-write security scan, 500 capacity, auto-extract on conversation end, strict format validation |
| 📋 | **Custom Files** | SOUL.md (persona, never compressed) + AGENT.md (behavior rules, built-in fallback) + USER.md (user profile, workspace only, skipped if absent) |
| 🌐 | **MCP Protocol Extension** | JSON-RPC 2.0 over stdio, dynamic tool discovery, Shadowing protection |
@@ -308,7 +300,7 @@ npm run dist # 构建 Windows 安装包
## 🔧 Tool List
-📁 File System (16)
+📁 File System (13)
| Tool | Function |
|------|------|
@@ -321,11 +313,8 @@ npm run dist # 构建 Windows 安装包
| `move_file` | Move/rename file |
| `copy_file` | Copy file |
| `edit_file` | Edit file (regex support) |
-| `get_file_info` | Get file metadata |
| `tree` | Directory tree (default 5 levels) |
| `download_file` | Download file |
-| `diff_files` | File diff comparison |
-| `replace_in_files` | Batch replace |
| `read_multiple_files` | Batch read (50 files/10KB) |
| `compress` | Compress file/directory |
@@ -398,16 +387,11 @@ npm run dist # 构建 Windows 安装包
-🕐 System Tools (6)
+🕐 System Tools (1)
| Tool | Function |
|------|------|
-| `datetime` | Get precise system time (ISO/Unix/date/time/full) |
| `calculator` | Safe math evaluation (+ - * / ** % (), recursive descent parser) |
-| `random` | Random generation (int/float/pick/string) |
-| `uuid` | Generate UUID v4 unique identifier |
-| `json_format` | JSON format + validate + sort keys |
-| `hash` | Cryptographic hash (MD5/SHA1/SHA256/SHA384/SHA512) |
@@ -433,7 +417,7 @@ User message → workspace SOUL.md (never compressed) → AGENT.md → USER.md (
↓
Ollama API (Streaming Response, num_ctx user-configurable 128K/256K/512K/1M)
↓
- Tool Registry (40 Built-in + MCP Dynamic + Plan Mode plan_track)
+ Tool Registry (32 Built-in + MCP Dynamic + Plan Mode plan_track)
↓
Hook System (pre/post tool/iteration/completion) → Observation → Reflection → Loop / Final Answer
```
@@ -501,7 +485,7 @@ npm start
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist
```
-Output: `release/Metona Ollama Setup v0.16.11.exe`
+Output: `release/Metona Ollama Setup v0.16.12.exe`
## 🛠️ Common Commands
diff --git a/package-lock.json b/package-lock.json
index ef6323a..701212d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "metona-ollama-desktop",
- "version": "0.16.11",
+ "version": "0.16.12",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "metona-ollama-desktop",
- "version": "0.16.11",
+ "version": "0.16.12",
"license": "MIT",
"dependencies": {
"ffmpeg-static": "^5.2.0",
diff --git a/package.json b/package.json
index bd6906f..6155c54 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "metona-ollama-desktop",
- "version": "0.16.11",
+ "version": "0.16.12",
"description": "Metona Ollama - TypeScript + Electron 桌面 AI 聊天客户端",
"main": "dist/main/main.js",
"author": "thzxx",
diff --git a/src/main/ipc.ts b/src/main/ipc.ts
index bcb1c4d..41e6949 100644
--- a/src/main/ipc.ts
+++ b/src/main/ipc.ts
@@ -36,20 +36,12 @@ import {
handleWebFetch,
handleWebSearch,
handleEditFile,
- handleGetFileInfo,
handleTree,
handleDownloadFile,
- handleDiffFiles,
- handleReplaceInFiles,
handleReadMultipleFiles,
handleGit,
handleCompress,
- handleDateTime,
- handleCalculator,
- handleRandom,
- handleUUID,
- handleJsonFormat,
- handleHash
+ handleCalculator
} from './tool-handlers.js';
import { browserOpen, browserScreenshot, browserEvaluate, browserExtract, browserClick, browserType, browserScroll, browserClose, browserWait } from './browser.js';
import { startServer, stopServer, stopAllServers, callTool, getAllTools, getServerStatuses, refreshTools, setMCPTimeout } from './mcp-manager.js';
@@ -80,20 +72,12 @@ function summarizeResult(toolName: string, result: Record): str
case 'web_fetch': return `${result.status} | ${result.length} chars`;
case 'web_search': return `[${String(result._mode) === 'searxng' ? 'SearXNG' : '内置'}] "${result.query}" → ${result.total} 条结果`;
case 'edit_file': return `${result.path} (${result.replaceCount} 处替换)`;
- case 'get_file_info': return `${result.name} (${result.type}, ${result.size}B)`;
case 'tree': return `${result.path} (${result.fileCount} 文件 / ${result.dirCount} 目录)`;
case 'download_file': return `${result.url} → ${result.destination} (${result.size}B)`;
- case 'diff_files': return result.hasChanges ? `${result.file1} ↔ ${result.file2} (有差异)` : '无差异';
- case 'replace_in_files': return `${result.filesChanged} 文件 / ${result.totalReplacements} 处替换`;
case 'read_multiple_files': return `${result.total} 个文件`;
case 'git': return `${result.action} ✓`;
case 'compress': return `${result.action} → ${result.archive || result.destination}`;
- case 'datetime': return `${result.date || result.iso}`;
case 'calculator': return `${result.expression} = ${result.result}`;
- case 'random': return `${String(result.type) === 'pick' ? '🎲 ' : ''}${result.result}`;
- case 'uuid': return `${result.result}`;
- case 'json_format': return `${Number(result.keys) || 0} keys, ${result.formatted_size}B`;
- case 'hash': return `${result.algorithm}: ${String(result.hash ?? '').slice(0, 16)}...`;
default: return '完成';
}
}
@@ -212,20 +196,12 @@ export async function setupIPC(): Promise {
case 'web_fetch': result = await handleWebFetch(args as { url: string; max_chars?: number; extract_mode?: string; mobile_ua?: boolean; retry?: boolean }); break;
case 'web_search': result = await handleWebSearch(args as { query: string; max_results?: number; time_range?: string; enhance_snippets?: boolean; fetch_top?: number }); break;
case 'edit_file': result = await handleEditFile(args as { path: string; old_text: string; new_text: string; all?: boolean; use_regex?: boolean }); break;
- case 'get_file_info': result = await handleGetFileInfo(args as { path: string }); break;
case 'tree': result = await handleTree(args as { path: string; max_depth?: number; include_hidden?: boolean }); break;
case 'download_file': result = await handleDownloadFile(args as { url: string; destination: string }); break;
- case 'diff_files': result = await handleDiffFiles(args as { file1: string; file2: string; context_lines?: number }); break;
- case 'replace_in_files': result = await handleReplaceInFiles(args as { path: string; glob: string; old_text: string; new_text: string }); break;
case 'read_multiple_files':result = await handleReadMultipleFiles(args as { paths: string[]; max_chars_per_file?: number }); break;
case 'git': result = await handleGit(args as { action: string; path?: string; files?: string[]; message?: string; branch?: string; tag_name?: string; stash_sub?: string; remote?: string; remote_url?: string; count?: number; all?: boolean; staged?: boolean; new_branch?: boolean; delete_branch?: boolean; force?: boolean; url?: string }); break;
case 'compress': result = await handleCompress(args as { action: string; path: string; destination?: string; format?: string }); break;
- case 'datetime': result = handleDateTime(args as { format?: string; timezone?: string }); break;
case 'calculator': result = handleCalculator(args as { expression: string }); break;
- case 'random': result = handleRandom(args as { type?: string; min?: number; max?: number; count?: number; items?: string[]; length?: number }); break;
- case 'uuid': result = handleUUID(args as { count?: number }); break;
- case 'json_format': result = handleJsonFormat(args as { json: string; indent?: number; sort_keys?: boolean }); break;
- case 'hash': result = handleHash(args as { text: string; algorithm?: string }); break;
// v5.1 Browser 控制(增强版)
case 'browser_open': result = await browserOpen(args.url as string, args.wait_selector as string | undefined); break;
case 'browser_screenshot': result = await browserScreenshot({ full_page: args.full_page as boolean, selector: args.selector as string }); break;
diff --git a/src/main/menu.ts b/src/main/menu.ts
index c1da712..58d4f63 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.16.11',
+ message: 'Metona Ollama Desktop v0.16.12',
detail: 'TypeScript + Electron Ollama AI 聊天客户端\n\nhttps://gitee.com/thzxx/metona-ollama',
icon: getIconPath()
});
diff --git a/src/main/tool-handlers-fs.ts b/src/main/tool-handlers-fs.ts
index 940fdda..83b2340 100644
--- a/src/main/tool-handlers-fs.ts
+++ b/src/main/tool-handlers-fs.ts
@@ -5,7 +5,6 @@
import * as fs from 'fs/promises';
import * as path from 'path';
import * as os from 'os';
-import { spawn } from 'child_process';
import { checkPathAllowed } from './tool-security.js';
import { sendLog, resolvePath, type ToolResult } from './tool-handlers-shared.js';
import { getWorkspaceDir } from './workspace.js';
@@ -580,29 +579,6 @@ export async function handleEditFile(params: { path: string; old_text: string; n
}
}
-export async function handleGetFileInfo(params: { path: string }): Promise {
- try {
- const filePath = resolvePath(params.path);
- const allowed = checkPathAllowed(filePath, 'read');
- if (!allowed.ok) return { success: false, error: allowed.reason };
-
- const stat = await fs.stat(filePath);
- return {
- success: true,
- path: filePath,
- name: path.basename(filePath),
- type: stat.isDirectory() ? 'directory' : stat.isFile() ? 'file' : 'other',
- size: stat.size,
- created: stat.birthtime.toISOString(),
- modified: stat.mtime.toISOString(),
- accessed: stat.atime.toISOString(),
- permissions: (stat.mode & 0o777).toString(8)
- };
- } catch (err) {
- return { success: false, error: (err as Error).message };
- }
-}
-
export async function handleTree(params: { path: string; max_depth?: number; include_hidden?: boolean }): Promise {
try {
const rootPath = resolvePath(params.path);
@@ -659,185 +635,6 @@ export async function handleTree(params: { path: string; max_depth?: number; inc
}
}
-
-/** 运行 shell 命令并返回 stdout/stderr */
-function runShell(cmd: string, args: string[]): Promise<{ stdout: string; stderr: string; code: number }> {
- return new Promise((resolve) => {
- const proc = spawn(cmd, args, { stdio: ['pipe', 'pipe', 'pipe'] });
- let stdout = '', stderr = '';
- proc.stdout.on('data', (d: Buffer) => { stdout += d.toString(); });
- proc.stderr.on('data', (d: Buffer) => { stderr += d.toString(); });
- proc.on('close', (code) => resolve({ stdout, stderr, code: code ?? 1 }));
- proc.on('error', (err) => resolve({ stdout, stderr: err.message, code: 1 }));
- });
-}
-
-export async function handleDiffFiles(params: { file1: string; file2: string; context_lines?: number }): Promise {
- try {
- const path1 = resolvePath(params.file1);
- const path2 = resolvePath(params.file2);
- const check1 = checkPathAllowed(path1, 'read');
- if (!check1.ok) return { success: false, error: check1.reason };
- const check2 = checkPathAllowed(path2, 'read');
- if (!check2.ok) return { success: false, error: check2.reason };
-
- const contextSize = params.context_lines || 3;
- let diffOutput = '';
- let usedCommand = '';
-
- // 1) 优先使用系统 diff 命令
- const diffResult = await runShell('diff', ['-u', `-U${contextSize}`, path1, path2]);
- if (diffResult.code <= 1) { // diff exits 0=identical, 1=different
- diffOutput = diffResult.stdout;
- usedCommand = 'diff -u';
- }
-
- // 2) diff 不可用,尝试 git diff
- if (!usedCommand) {
- const gitResult = await runShell('git', ['diff', `--unified=${contextSize}`, '--no-index', path1, path2]);
- if (gitResult.code <= 1) {
- diffOutput = gitResult.stdout;
- usedCommand = 'git diff';
- }
- }
-
- // 3) 回退到内置简易 diff
- if (!usedCommand) {
- diffOutput = builtinDiff(path1, path2, contextSize);
- usedCommand = 'builtin';
- }
-
- const hasChanges = diffOutput.trim().length > 0;
- return {
- success: true,
- file1: path1,
- file2: path2,
- diff: diffOutput,
- hasChanges,
- method: usedCommand
- };
- } catch (err) {
- return { success: false, error: (err as Error).message };
- }
-}
-
-/** 内置简易 diff(回退方案) */
-function builtinDiff(path1: string, path2: string, contextSize: number): string {
- const fsSync = require('fs');
- const content1 = fsSync.readFileSync(path1, 'utf-8');
- const content2 = fsSync.readFileSync(path2, 'utf-8');
- const lines1 = content1.split('\n');
- const lines2 = content2.split('\n');
-
- const diffLines: string[] = [];
- let i = 0, j = 0;
- while (i < lines1.length || j < lines2.length) {
- if (i < lines1.length && j < lines2.length && lines1[i] === lines2[j]) {
- if (diffLines.length === 0 || diffLines[diffLines.length - 1].startsWith('-') || diffLines[diffLines.length - 1].startsWith('+')) {
- const ctxStart = Math.max(0, i - contextSize);
- for (let k = ctxStart; k < i; k++) diffLines.push(` ${lines1[k]}`);
- }
- diffLines.push(` ${lines1[i]}`);
- i++; j++;
- } else {
- let found = false;
- for (let look = 1; look <= 10 && i + look < lines1.length; look++) {
- if (lines1[i + look] === lines2[j]) {
- for (let k = 0; k < look; k++) diffLines.push(`-${lines1[i + k]}`);
- i += look; found = true; break;
- }
- }
- if (!found) {
- for (let look = 1; look <= 10 && j + look < lines2.length; look++) {
- if (lines1[i] === lines2[j + look]) {
- for (let k = 0; k < look; k++) diffLines.push(`+${lines2[j + k]}`);
- j += look; found = true; break;
- }
- }
- }
- if (!found) {
- if (i < lines1.length) { diffLines.push(`-${lines1[i]}`); i++; }
- if (j < lines2.length) { diffLines.push(`+${lines2[j]}`); j++; }
- }
- }
- }
- return diffLines.join('\n');
-}
-
-/** 将 glob 模式转换为正则表达式(支持 **, *, ?) */
-function globToRegex(glob: string): RegExp {
- // 转义正则特殊字符
- let pattern = glob.replace(/[.+^${}()|[\]\\]/g, '\\$&');
- // ** → 匹配任意路径(含 /)
- pattern = pattern.replace(/\*\*/g, '\u0000'); // 临时占位
- // * → 匹配除 / 外的任意字符
- pattern = pattern.replace(/\*/g, '[^/]*');
- // ? → 匹配除 / 外的单个字符
- pattern = pattern.replace(/\?/g, '[^/]');
- // 恢复 ** → .*
- pattern = pattern.replace(/\u0000/g, '.*');
- return new RegExp(`^${pattern}$`);
-}
-
-export async function handleReplaceInFiles(params: { path: string; glob: string; old_text: string; new_text: string }): Promise {
- try {
- const rootPath = resolvePath(params.path);
- const allowed = checkPathAllowed(rootPath, 'write');
- if (!allowed.ok) return { success: false, error: allowed.reason };
-
- // 正确的 glob → regex 转换:支持 ** (跨目录通配), * (单层通配), ? (单字符)
- const globRegex = globToRegex(params.glob);
-
- const results: Array<{ file: string; replacements: number }> = [];
- let totalReplacements = 0;
- const maxFiles = 200;
- let fileCount = 0;
-
- async function scanDir(dir: string): Promise {
- if (fileCount >= maxFiles) return;
- let items;
- try { items = await fs.readdir(dir, { withFileTypes: true }); } catch { return; }
-
- for (const item of items) {
- if (fileCount >= maxFiles) return;
- if (item.name.startsWith('.')) continue;
- const fullPath = path.join(dir, item.name);
- const relPath = path.relative(rootPath, fullPath);
-
- if (item.isDirectory()) {
- await scanDir(fullPath);
- } else if (globRegex.test(relPath) || globRegex.test(item.name)) {
- fileCount++;
- try {
- const content = await fs.readFile(fullPath, 'utf-8');
- if (content.includes(params.old_text)) {
- const parts = content.split(params.old_text);
- const count = parts.length - 1;
- const newContent = parts.join(params.new_text);
- await fs.writeFile(fullPath, newContent, 'utf-8');
- results.push({ file: path.relative(rootPath, fullPath), replacements: count });
- totalReplacements += count;
- }
- } catch { /* skip unreadable */ }
- }
- }
- }
-
- await scanDir(rootPath);
-
- return {
- success: true,
- pattern: params.glob,
- filesChanged: results.length,
- totalReplacements,
- results,
- truncated: fileCount >= maxFiles
- };
- } catch (err) {
- return { success: false, error: (err as Error).message };
- }
-}
-
export async function handleReadMultipleFiles(params: { paths: string[]; max_chars_per_file?: number }): Promise {
try {
const maxChars = params.max_chars_per_file || 0; // 0 = 不截断
diff --git a/src/main/tool-handlers-system.ts b/src/main/tool-handlers-system.ts
index a15ba02..0505edb 100644
--- a/src/main/tool-handlers-system.ts
+++ b/src/main/tool-handlers-system.ts
@@ -5,7 +5,6 @@
import * as fs from 'fs/promises';
import * as path from 'path';
import { spawn } from 'child_process';
-import * as crypto from 'crypto';
import { checkPathAllowed, checkCommandAllowed } from './tool-security.js';
import { mainWindow } from './main.js';
import { sendLog, resolvePath, type ToolResult } from './tool-handlers-shared.js';
@@ -1553,85 +1552,6 @@ export async function handleCompress(params: { action: string; path: string; des
}
}
-// ── datetime: 获取系统精确时间 ──
-export function handleDateTime(params: { format?: string; timezone?: string }): ToolResult {
- try {
- const now = new Date();
- const tz = params.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
- const fmt = params.format || 'full';
-
- const iso = now.toISOString();
- const unixSec = Math.floor(now.getTime() / 1000);
- const unixMs = now.getTime();
-
- // 中文日期(无前导零月份)
- const y = now.getFullYear();
- const m = now.getMonth() + 1;
- const d = now.getDate();
- const weekdays = ['日', '一', '二', '三', '四', '五', '六'];
- const weekday = `星期${weekdays[now.getDay()]}`;
- const dateStr = `${y}年${m}月${d}日 ${weekday}`;
-
- // 24小时制时间
- const h = now.getHours();
- const min = now.getMinutes();
- const sec = now.getSeconds();
- const time24 = `${String(h).padStart(2, '0')}:${String(min).padStart(2, '0')}:${String(sec).padStart(2, '0')}`;
-
- // 时段
- const period = h < 6 ? '凌晨' : h < 12 ? '上午' : h < 14 ? '中午' : h < 18 ? '下午' : '晚上';
-
- // 人性化时间(12小时制 + 时段)
- const h12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
- const timeFriendly = `${period} ${h12}:${String(min).padStart(2, '0')}`;
-
- // 完整人性化字符串
- const friendly = `${y}年${m}月${d}日 ${weekday} ${timeFriendly}`;
-
- let result: Record;
-
- switch (fmt) {
- case 'iso':
- result = { iso, timezone: tz };
- break;
- case 'unix':
- result = { unix_seconds: unixSec, unix_milliseconds: unixMs, timezone: tz };
- break;
- case 'date':
- result = { date: dateStr, weekday, timezone: tz };
- break;
- case 'time':
- result = { time_24h: time24, period, time_friendly: timeFriendly, timezone: tz };
- break;
- default: // full
- result = {
- iso,
- unix_seconds: unixSec,
- unix_milliseconds: unixMs,
- date: dateStr,
- weekday,
- time_24h: time24,
- period,
- time_friendly: timeFriendly,
- friendly,
- timezone: tz,
- year: y,
- month: m,
- day: d,
- hour: h,
- minute: min,
- second: sec,
- millisecond: now.getMilliseconds(),
- day_of_week: now.getDay(),
- };
- }
-
- return { success: true, ...result };
- } catch (err) {
- return { success: false, error: (err as Error).message };
- }
-}
-
// ── calculator: 安全数学计算器(纯 JS 递归下降解析,无 eval) ──
export function handleCalculator(params: { expression: string }): ToolResult {
try {
@@ -1710,126 +1630,3 @@ function safeCalc(expr: string): number {
if (!isFinite(result)) throw new Error('计算结果为无穷大');
return result;
}
-
-// ── random: 随机数生成 ──
-export function handleRandom(params: { type?: string; min?: number; max?: number; count?: number; items?: string[]; length?: number }): ToolResult {
- try {
- const type = params.type || 'int';
-
- switch (type) {
- case 'int': {
- const min = params.min ?? 0;
- const max = params.max ?? 100;
- if (min > max) return { success: false, error: 'min 不能大于 max' };
- const count = Math.min(params.count ?? 1, 100);
- if (count === 1) {
- return { success: true, type: 'int', result: Math.floor(Math.random() * (max - min + 1)) + min, range: [min, max] };
- }
- const results: number[] = [];
- for (let i = 0; i < count; i++) results.push(Math.floor(Math.random() * (max - min + 1)) + min);
- return { success: true, type: 'int', results, count, range: [min, max] };
- }
- case 'float': {
- const min = params.min ?? 0;
- const max = params.max ?? 1;
- if (min > max) return { success: false, error: 'min 不能大于 max' };
- const count = Math.min(params.count ?? 1, 100);
- if (count === 1) {
- return { success: true, type: 'float', result: Number((Math.random() * (max - min) + min).toFixed(6)), range: [min, max] };
- }
- const results: number[] = [];
- for (let i = 0; i < count; i++) results.push(Number((Math.random() * (max - min) + min).toFixed(6)));
- return { success: true, type: 'float', results, count, range: [min, max] };
- }
- case 'pick': {
- const items = params.items;
- if (!items || items.length === 0) return { success: false, error: 'pick 类型需要提供 items 数组' };
- const count = Math.min(params.count ?? 1, items.length);
- // Fisher-Yates 部分洗牌
- const pool = [...items];
- const picked: string[] = [];
- for (let i = 0; i < count; i++) {
- const idx = Math.floor(Math.random() * pool.length);
- picked.push(pool[idx]);
- pool.splice(idx, 1);
- }
- return { success: true, type: 'pick', result: count === 1 ? picked[0] : picked, from: items.length, count };
- }
- case 'string': {
- const length = Math.min(params.length ?? 8, 256);
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
- // 使用 crypto 增强随机性
- const buf = crypto.randomBytes(length);
- let result = '';
- for (let i = 0; i < length; i++) {
- result += chars.charAt(buf[i] % chars.length);
- }
- return { success: true, type: 'string', result, length, charset: 'alphanumeric' };
- }
- default:
- return { success: false, error: `未知类型: ${type}。支持: int / float / pick / string` };
- }
- } catch (err) {
- return { success: false, error: (err as Error).message };
- }
-}
-
-// ── uuid: 生成 UUID v4 ──
-export function handleUUID(params: { count?: number }): ToolResult {
- try {
- const crypto = require('crypto');
- const count = Math.min(params.count ?? 1, 20);
- if (count === 1) {
- return { success: true, result: crypto.randomUUID() };
- }
- const results: string[] = [];
- for (let i = 0; i < count; i++) results.push(crypto.randomUUID());
- return { success: true, results, count };
- } catch (err) {
- return { success: false, error: (err as Error).message };
- }
-}
-
-// ── json_format: JSON 格式化 + 验证 ──
-export function handleJsonFormat(params: { json: string; indent?: number; sort_keys?: boolean }): ToolResult {
- try {
- const parsed = JSON.parse(params.json);
- const indent = params.indent ?? 2;
- let formatted: string;
- if (params.sort_keys) {
- formatted = JSON.stringify(sortObjectKeys(parsed), null, indent);
- } else {
- formatted = JSON.stringify(parsed, null, indent);
- }
- const size = Buffer.byteLength(formatted, 'utf-8');
- return { success: true, formatted, original_size: params.json.length, formatted_size: size, keys: Object.keys(parsed).length };
- } catch (err) {
- return { success: false, error: `JSON 解析失败: ${(err as Error).message}`, input_preview: params.json.slice(0, 200) };
- }
-
- function sortObjectKeys(obj: any): any {
- if (Array.isArray(obj)) return obj.map(sortObjectKeys);
- if (obj !== null && typeof obj === 'object') {
- const sorted: Record = {};
- for (const k of Object.keys(obj).sort()) sorted[k] = sortObjectKeys(obj[k]);
- return sorted;
- }
- return obj;
- }
-}
-
-// ── hash: 哈希计算 ──
-export function handleHash(params: { text: string; algorithm?: string }): ToolResult {
- try {
- const crypto = require('crypto');
- const algo = (params.algorithm || 'sha256').toLowerCase().replace('-', '');
- const validAlgos = ['md5', 'sha1', 'sha256', 'sha384', 'sha512'];
- if (!validAlgos.includes(algo)) {
- return { success: false, error: `不支持的算法: ${algo}。支持: ${validAlgos.join(', ')}` };
- }
- const hash = crypto.createHash(algo).update(params.text, 'utf-8').digest('hex');
- return { success: true, algorithm: algo, hash, input_length: params.text.length };
- } catch (err) {
- return { success: false, error: (err as Error).message };
- }
-}
diff --git a/src/main/tool-handlers.ts b/src/main/tool-handlers.ts
index 3ba5833..3881ace 100644
--- a/src/main/tool-handlers.ts
+++ b/src/main/tool-handlers.ts
@@ -6,7 +6,7 @@
export { sendLog, resolvePath, type ToolResult } from './tool-handlers-shared.js';
-// 文件系统操作(15 个)
+// 文件系统操作(11 个)
export {
handleReadFile,
handleWriteFile,
@@ -17,14 +17,11 @@ export {
handleMoveFile,
handleCopyFile,
handleEditFile,
- handleGetFileInfo,
handleTree,
- handleDiffFiles,
- handleReplaceInFiles,
handleReadMultipleFiles,
} from './tool-handlers-fs.js';
-// 系统与网络操作(8 个)
+// 系统与网络操作(7 个)
export {
handleRunCommand,
killToolProcess,
@@ -32,12 +29,7 @@ export {
handleWebSearch,
handleDownloadFile,
handleCompress,
- handleDateTime,
handleCalculator,
- handleRandom,
- handleUUID,
- handleJsonFormat,
- handleHash,
} from './tool-handlers-system.js';
// Git 操作(1 个)
diff --git a/src/renderer/components/tools-modal.ts b/src/renderer/components/tools-modal.ts
index f7aaa40..9c01b28 100644
--- a/src/renderer/components/tools-modal.ts
+++ b/src/renderer/components/tools-modal.ts
@@ -21,7 +21,7 @@ const MODE_NAMES: Record = {
const MANAGED_TOOLS = [
'run_command',
'write_file', 'create_directory', 'delete_file',
- 'edit_file', 'replace_in_files', 'move_file', 'copy_file',
+ 'edit_file', 'move_file', 'copy_file',
'download_file', 'compress',
];
diff --git a/src/renderer/components/workspace-panel.ts b/src/renderer/components/workspace-panel.ts
index d4d6e98..8175fd4 100644
--- a/src/renderer/components/workspace-panel.ts
+++ b/src/renderer/components/workspace-panel.ts
@@ -996,8 +996,8 @@ function getToolDisplayName(name: string): string {
read_file: '读取文件', write_file: '写入文件', list_directory: '列出目录',
search_files: '搜索文件', create_directory: '创建目录', delete_file: '删除文件', run_command: '执行命令',
move_file: '移动文件', copy_file: '复制文件', web_fetch: '网页抓取',
- edit_file: '编辑文件', get_file_info: '文件信息', tree: '目录树', download_file: '下载文件',
- diff_files: '文件对比', replace_in_files: '批量替换', read_multiple_files: '批量读取',
+ edit_file: '编辑文件', tree: '目录树', download_file: '下载文件',
+ read_multiple_files: '批量读取',
git: 'Git 操作', compress: '压缩/解压', web_search: '联网搜索',
memory: '记忆管理',
@@ -1013,8 +1013,8 @@ function getToolIcon(name: string): string {
read_file: '📄', write_file: '✏️', list_directory: '📁',
search_files: '🔍', create_directory: '📂', delete_file: '🗑️', run_command: '💻',
move_file: '📦', copy_file: '📋', web_fetch: '🌐',
- edit_file: '✂️', get_file_info: 'ℹ️', tree: '🌳', download_file: '⬇️',
- diff_files: '🔀', replace_in_files: '🔄', read_multiple_files: '📚',
+ edit_file: '✂️', tree: '🌳', download_file: '⬇️',
+ read_multiple_files: '📚',
git: '🔖', compress: '🗜️', web_search: '🔍',
memory: '🧠',
diff --git a/src/renderer/index.html b/src/renderer/index.html
index 7be3b82..27381a3 100644
--- a/src/renderer/index.html
+++ b/src/renderer/index.html
@@ -28,7 +28,7 @@