refactor(core): 移除不再需要的文件系统工具

删除以下工具及相关文件:
- copy_file: 复制文件
- create_directory: 创建目录
- delete_file: 删除文件
- move_file: 移动文件
- search_files: 搜索文件

清理范围:
- 工具实现文件 (5个)
- 工具描述文件 (5个)
- 单元测试文件 (6个)
- Agent presets 中的引用
- Checkpoint 系统中的触发类型
- Hook 系统中的相关处理
This commit is contained in:
2025-12-17 12:00:46 +08:00
parent 48b458bb9a
commit 2abea47386
34 changed files with 4 additions and 1731 deletions
@@ -1 +0,0 @@
复制文件或目录。支持递归复制整个目录结构。
@@ -1 +0,0 @@
创建新目录。支持递归创建父目录。如果目录已存在则不会报错。
@@ -1 +0,0 @@
删除文件或目录。删除目录时可以选择是否递归删除。需要谨慎使用。
@@ -1 +0,0 @@
移动或重命名文件/目录。可以将文件移动到新位置或更改文件名。
@@ -1 +0,0 @@
在目录中搜索匹配模式的文件
@@ -1,140 +0,0 @@
import * as fs from 'fs/promises';
import * as path from 'path';
import type { ToolResult } from '../../types/index.js';
import type { ToolWithMetadata } from '../types.js';
import { loadDescription } from '../load_description.js';
import { getPermissionManager } from '../../permission/index.js';
async function copyRecursive(source: string, dest: string): Promise<void> {
const stats = await fs.stat(source);
if (stats.isDirectory()) {
await fs.mkdir(dest, { recursive: true });
const entries = await fs.readdir(source);
for (const entry of entries) {
await copyRecursive(path.join(source, entry), path.join(dest, entry));
}
} else {
await fs.copyFile(source, dest);
}
}
export const copyFileTool: ToolWithMetadata = {
name: 'copy_file',
description: loadDescription('copy_file'),
metadata: {
name: 'copy_file',
category: 'filesystem',
description: '复制文件或目录',
keywords: ['copy', 'file', 'cp', 'duplicate', '复制', '文件', '拷贝'],
deferLoading: true,
},
parameters: {
source: {
type: 'string',
description: '源文件或目录的路径',
required: true,
},
destination: {
type: 'string',
description: '目标路径',
required: true,
},
},
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
const source = params.source as string;
const destination = params.destination as string;
const cwd = process.cwd();
const absoluteSource = path.isAbsolute(source)
? source
: path.join(cwd, source);
const absoluteDest = path.isAbsolute(destination)
? destination
: path.join(cwd, destination);
// 权限检查 - 源文件需要 read 权限
const permissionManager = getPermissionManager();
const sourcePermResult = await permissionManager.checkFilePermission({
operation: 'read',
path: absoluteSource,
workdir: cwd,
});
if (!sourcePermResult.allowed) {
if (sourcePermResult.needsConfirmation) {
return {
success: false,
output: '',
error: `需要用户确认: 读取 ${absoluteSource}\n原因: ${sourcePermResult.reason || '需要权限确认'}`,
};
}
return {
success: false,
output: '',
error: `权限被拒绝: ${sourcePermResult.reason || '不允许读取此文件'}`,
};
}
// 权限检查 - 目标位置需要 copy 权限
const destPermResult = await permissionManager.checkFilePermission({
operation: 'copy',
path: absoluteDest,
workdir: cwd,
});
if (!destPermResult.allowed) {
if (destPermResult.needsConfirmation) {
return {
success: false,
output: '',
error: `需要用户确认: 复制到 ${absoluteDest}\n原因: ${destPermResult.reason || '需要权限确认'}`,
};
}
return {
success: false,
output: '',
error: `权限被拒绝: ${destPermResult.reason || '不允许复制到此位置'}`,
};
}
try {
// 检查源文件是否存在
const sourceStats = await fs.stat(absoluteSource);
// 检查目标是否是目录
let finalDest = absoluteDest;
try {
const destStats = await fs.stat(absoluteDest);
if (destStats.isDirectory()) {
// 如果目标是目录,将源文件复制到该目录下
finalDest = path.join(absoluteDest, path.basename(absoluteSource));
}
} catch {
// 目标不存在,直接使用目标路径
}
// 确保目标目录存在
await fs.mkdir(path.dirname(finalDest), { recursive: true });
// 执行复制
if (sourceStats.isDirectory()) {
await copyRecursive(absoluteSource, finalDest);
} else {
await fs.copyFile(absoluteSource, finalDest);
}
return {
success: true,
output: `已复制: ${absoluteSource} -> ${finalDest}`,
};
} catch (error) {
return {
success: false,
output: '',
error: error instanceof Error ? error.message : String(error),
};
}
},
};
@@ -1,91 +0,0 @@
import * as fs from 'fs/promises';
import * as path from 'path';
import type { ToolResult } from '../../types/index.js';
import type { ToolWithMetadata } from '../types.js';
import { loadDescription } from '../load_description.js';
import { getPermissionManager } from '../../permission/index.js';
export const createDirectoryTool: ToolWithMetadata = {
name: 'create_directory',
description: loadDescription('create_directory'),
metadata: {
name: 'create_directory',
category: 'filesystem',
description: '创建目录',
keywords: ['create', 'directory', 'mkdir', 'folder', 'new', '创建', '目录', '文件夹', '新建'],
deferLoading: true,
},
parameters: {
path: {
type: 'string',
description: '要创建的目录路径',
required: true,
},
},
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
const dirPath = params.path as string;
const cwd = process.cwd();
const absolutePath = path.isAbsolute(dirPath)
? dirPath
: path.join(cwd, dirPath);
// 权限检查
const permissionManager = getPermissionManager();
const permResult = await permissionManager.checkFilePermission({
operation: 'mkdir',
path: absolutePath,
workdir: cwd,
});
if (!permResult.allowed) {
if (permResult.needsConfirmation) {
return {
success: false,
output: '',
error: `需要用户确认: 创建目录 ${absolutePath}\n原因: ${permResult.reason || '需要权限确认'}`,
};
}
return {
success: false,
output: '',
error: `权限被拒绝: ${permResult.reason || '不允许创建此目录'}`,
};
}
try {
// 检查目录是否已存在
try {
const stats = await fs.stat(absolutePath);
if (stats.isDirectory()) {
return {
success: true,
output: `目录已存在: ${absolutePath}`,
};
} else {
return {
success: false,
output: '',
error: `路径已存在且不是目录: ${absolutePath}`,
};
}
} catch {
// 目录不存在,继续创建
}
// 创建目录(递归创建父目录)
await fs.mkdir(absolutePath, { recursive: true });
return {
success: true,
output: `已创建目录: ${absolutePath}`,
};
} catch (error) {
return {
success: false,
output: '',
error: error instanceof Error ? error.message : String(error),
};
}
},
};
@@ -1,99 +0,0 @@
import * as fs from 'fs/promises';
import * as path from 'path';
import type { ToolResult } from '../../types/index.js';
import type { ToolWithMetadata } from '../types.js';
import { loadDescription } from '../load_description.js';
import { getPermissionManager } from '../../permission/index.js';
export const deleteFileTool: ToolWithMetadata = {
name: 'delete_file',
description: loadDescription('delete_file'),
metadata: {
name: 'delete_file',
category: 'filesystem',
description: '删除文件或目录',
keywords: ['delete', 'remove', 'file', 'rm', '删除', '移除', '文件'],
deferLoading: true,
},
parameters: {
path: {
type: 'string',
description: '要删除的文件或目录的路径',
required: true,
},
recursive: {
type: 'boolean',
description: '是否递归删除目录(默认 false',
required: false,
},
},
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
const filePath = params.path as string;
const recursive = (params.recursive as boolean) || false;
const cwd = process.cwd();
const absolutePath = path.isAbsolute(filePath)
? filePath
: path.join(cwd, filePath);
// 权限检查
const permissionManager = getPermissionManager();
const permResult = await permissionManager.checkFilePermission({
operation: 'delete',
path: absolutePath,
workdir: cwd,
});
if (!permResult.allowed) {
if (permResult.needsConfirmation) {
return {
success: false,
output: '',
error: `需要用户确认: 删除 ${absolutePath}\n原因: ${permResult.reason || '需要权限确认'}`,
};
}
return {
success: false,
output: '',
error: `权限被拒绝: ${permResult.reason || '不允许删除此文件'}`,
};
}
try {
const stats = await fs.stat(absolutePath);
if (stats.isDirectory()) {
if (!recursive) {
// 检查目录是否为空
const entries = await fs.readdir(absolutePath);
if (entries.length > 0) {
return {
success: false,
output: '',
error: `目录不为空。如需删除非空目录,请设置 recursive: true`,
};
}
await fs.rmdir(absolutePath);
} else {
await fs.rm(absolutePath, { recursive: true });
}
return {
success: true,
output: `已删除目录: ${absolutePath}`,
};
} else {
await fs.unlink(absolutePath);
return {
success: true,
output: `已删除文件: ${absolutePath}`,
};
}
} catch (error) {
return {
success: false,
output: '',
error: error instanceof Error ? error.message : String(error),
};
}
},
};
@@ -6,17 +6,10 @@ export { multiEditTool } from './multi_edit.js';
// 目录操作
export { listDirTool } from './list_directory.js';
export { createDirectoryTool } from './create_directory.js';
// 搜索
export { searchFilesTool } from './search_files.js';
export { globTool } from './glob.js';
export { grepTool } from './grep.js';
// 文件信息
export { getFileInfoTool } from './get_file_info.js';
// 文件管理
export { moveFileTool } from './move_file.js';
export { copyFileTool } from './copy_file.js';
export { deleteFileTool } from './delete_file.js';
@@ -1,122 +0,0 @@
import * as fs from 'fs/promises';
import * as path from 'path';
import type { ToolResult } from '../../types/index.js';
import type { ToolWithMetadata } from '../types.js';
import { loadDescription } from '../load_description.js';
import { getPermissionManager } from '../../permission/index.js';
export const moveFileTool: ToolWithMetadata = {
name: 'move_file',
description: loadDescription('move_file'),
metadata: {
name: 'move_file',
category: 'filesystem',
description: '移动或重命名文件/目录',
keywords: ['move', 'rename', 'file', 'mv', '移动', '重命名', '文件'],
deferLoading: true,
},
parameters: {
source: {
type: 'string',
description: '源文件或目录的路径',
required: true,
},
destination: {
type: 'string',
description: '目标路径',
required: true,
},
},
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
const source = params.source as string;
const destination = params.destination as string;
const cwd = process.cwd();
const absoluteSource = path.isAbsolute(source)
? source
: path.join(cwd, source);
const absoluteDest = path.isAbsolute(destination)
? destination
: path.join(cwd, destination);
// 权限检查 - 源文件需要 move 权限
const permissionManager = getPermissionManager();
const sourcePermResult = await permissionManager.checkFilePermission({
operation: 'move',
path: absoluteSource,
workdir: cwd,
});
if (!sourcePermResult.allowed) {
if (sourcePermResult.needsConfirmation) {
return {
success: false,
output: '',
error: `需要用户确认: 移动 ${absoluteSource}\n原因: ${sourcePermResult.reason || '需要权限确认'}`,
};
}
return {
success: false,
output: '',
error: `权限被拒绝: ${sourcePermResult.reason || '不允许移动此文件'}`,
};
}
// 权限检查 - 目标位置需要 write 权限
const destPermResult = await permissionManager.checkFilePermission({
operation: 'write',
path: absoluteDest,
workdir: cwd,
});
if (!destPermResult.allowed) {
if (destPermResult.needsConfirmation) {
return {
success: false,
output: '',
error: `需要用户确认: 写入到 ${absoluteDest}\n原因: ${destPermResult.reason || '需要权限确认'}`,
};
}
return {
success: false,
output: '',
error: `权限被拒绝: ${destPermResult.reason || '不允许写入到此位置'}`,
};
}
try {
// 检查源文件是否存在
await fs.access(absoluteSource);
// 检查目标是否是目录
let finalDest = absoluteDest;
try {
const destStats = await fs.stat(absoluteDest);
if (destStats.isDirectory()) {
// 如果目标是目录,将源文件移动到该目录下
finalDest = path.join(absoluteDest, path.basename(absoluteSource));
}
} catch {
// 目标不存在,直接使用目标路径
}
// 确保目标目录存在
await fs.mkdir(path.dirname(finalDest), { recursive: true });
// 执行移动
await fs.rename(absoluteSource, finalDest);
return {
success: true,
output: `已移动: ${absoluteSource} -> ${finalDest}`,
};
} catch (error) {
return {
success: false,
output: '',
error: error instanceof Error ? error.message : String(error),
};
}
},
};
@@ -1,107 +0,0 @@
import * as fs from 'fs/promises';
import * as path from 'path';
import type { ToolResult } from '../../types/index.js';
import type { ToolWithMetadata } from '../types.js';
import { loadDescription } from '../load_description.js';
import { getPermissionManager } from '../../permission/index.js';
export const searchFilesTool: ToolWithMetadata = {
name: 'search_files',
description: loadDescription('search_files'),
metadata: {
name: 'search_files',
category: 'filesystem',
description: '按文件名搜索文件',
keywords: ['search', 'file', 'find', 'glob', 'pattern', '搜索', '文件', '查找', '匹配'],
deferLoading: true,
},
parameters: {
directory: {
type: 'string',
description: '搜索的起始目录',
required: true,
},
pattern: {
type: 'string',
description: '文件名匹配模式(支持 glob 模式,如 *.ts',
required: true,
},
},
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
const directory = params.directory as string;
const pattern = params.pattern as string;
const cwd = process.cwd();
const absolutePath = path.isAbsolute(directory)
? directory
: path.join(cwd, directory);
// 权限检查
const permissionManager = getPermissionManager();
const permResult = await permissionManager.checkFilePermission({
operation: 'search',
path: absolutePath,
workdir: cwd,
});
if (!permResult.allowed) {
if (permResult.needsConfirmation) {
return {
success: false,
output: '',
error: `需要用户确认: 搜索目录 ${absolutePath}\n原因: ${permResult.reason || '需要权限确认'}`,
};
}
return {
success: false,
output: '',
error: `权限被拒绝: ${permResult.reason || '不允许搜索此目录'}`,
};
}
const matches: string[] = [];
const regex = new RegExp(
pattern.replace(/\*/g, '.*').replace(/\?/g, '.'),
'i'
);
async function searchRecursive(dir: string, depth = 0): Promise<void> {
if (depth > 10) return;
try {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.name.startsWith('.') || entry.name === 'node_modules') {
continue;
}
if (entry.isDirectory()) {
await searchRecursive(fullPath, depth + 1);
} else if (regex.test(entry.name)) {
matches.push(fullPath);
}
}
} catch {
// 忽略权限错误
}
}
try {
await searchRecursive(absolutePath);
return {
success: true,
output:
matches.length > 0
? matches.join('\n')
: '没有找到匹配的文件',
};
} catch (error) {
return {
success: false,
output: '',
error: error instanceof Error ? error.message : String(error),
};
}
},
};
-10
View File
@@ -21,14 +21,9 @@ import {
editFileTool,
multiEditTool,
listDirTool,
createDirectoryTool,
searchFilesTool,
globTool,
grepTool,
getFileInfoTool,
moveFileTool,
copyFileTool,
deleteFileTool,
} from './filesystem/index.js';
// Web 工具
@@ -87,14 +82,9 @@ const allToolsWithMetadata: ToolWithMetadata[] = [
editFileTool,
multiEditTool,
listDirTool,
createDirectoryTool,
searchFilesTool,
globTool,
grepTool,
getFileInfoTool,
moveFileTool,
copyFileTool,
deleteFileTool,
// Web 工具 (deferLoading: false)
webSearchTool,
@@ -15,14 +15,9 @@ const TOOL_CATEGORY_MAP: Record<string, string> = {
edit_file: 'filesystem',
multi_edit: 'filesystem',
list_directory: 'filesystem',
create_directory: 'filesystem',
search_files: 'filesystem',
glob: 'filesystem',
grep: 'filesystem',
get_file_info: 'filesystem',
move_file: 'filesystem',
copy_file: 'filesystem',
delete_file: 'filesystem',
// web
web_search: 'web',
web_extract: 'web',