feat: 优化鲁班角色并完善ToolSandbox工具开发体系
## 鲁班角色优化 - 新增tool-best-practices知识模块:工具设计最佳实践、性能优化、安全防护 - 新增dpml-tool-tagging知识模块:DPML工具标签四组件架构精通 - 增强craftsmanship思维模式:现代工具特征完善 - 资源迁移到包级别:支持跨项目共享 ## ToolSandbox架构完善 - 实现ToolSandbox类:支持@tool://协议的三阶段执行流程 - 优化依赖管理:getDependencies()接口标准化 - 完善UserProtocol:支持@user://沙箱目录访问 - 增强工具发现:FilePatternDiscovery支持多种文件模式 ## 工具生态建设 - 添加tool.tag.md:DPML工具标签框架完整定义 - 重构ToolInterface:统一getDependencies()接口规范 - 优化ToolExecutor:集成ToolSandbox执行流程 - 更新注册表:29个资源完整注册发现 ## 技术架构改进 - pnpm依赖集成:自动化沙箱环境管理 - 协议系统完善:@tool://和@user://协议标准化 - 资源结构统一:包级别和项目级别一致性 - 开发流程标准化:从需求分析到质量保证的完整工作流 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
517
src/lib/tool/ToolSandbox.js
Normal file
517
src/lib/tool/ToolSandbox.js
Normal file
@ -0,0 +1,517 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
const { spawn } = require('child_process');
|
||||
const vm = require('vm');
|
||||
|
||||
/**
|
||||
* ToolSandbox - 工具沙箱环境管理器
|
||||
*
|
||||
* 基于现有协议系统的工具执行环境,支持:
|
||||
* - @tool:// 协议定位工具
|
||||
* - @user://.promptx/toolbox 沙箱隔离
|
||||
* - 自动依赖管理
|
||||
* - 可复用的执行环境
|
||||
*/
|
||||
class ToolSandbox {
|
||||
constructor(toolReference, options = {}) {
|
||||
this.toolReference = toolReference; // @tool://url-validator
|
||||
this.resourceManager = null; // ResourceManager实例
|
||||
this.toolId = null; // 工具ID,如 url-validator
|
||||
this.toolContent = null; // 工具文件内容
|
||||
this.toolInstance = null; // 工具实例
|
||||
this.dependencies = []; // 依赖列表
|
||||
this.sandboxPath = null; // 沙箱目录路径
|
||||
this.sandboxContext = null; // VM沙箱上下文
|
||||
|
||||
// 状态标志
|
||||
this.isAnalyzed = false;
|
||||
this.isPrepared = false;
|
||||
|
||||
// 配置选项
|
||||
this.options = {
|
||||
timeout: 30000,
|
||||
enableDependencyInstall: true,
|
||||
forceReinstall: false,
|
||||
...options
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置ResourceManager实例
|
||||
* @param {ResourceManager} resourceManager
|
||||
*/
|
||||
setResourceManager(resourceManager) {
|
||||
this.resourceManager = resourceManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析工具:加载工具内容,提取元信息和依赖
|
||||
* @returns {Promise<Object>} 分析结果
|
||||
*/
|
||||
async analyze() {
|
||||
if (this.isAnalyzed) {
|
||||
return this.getAnalysisResult();
|
||||
}
|
||||
|
||||
if (!this.resourceManager) {
|
||||
throw new Error('ResourceManager not set. Call setResourceManager() first.');
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. 解析工具引用,提取工具ID
|
||||
this.toolId = this.extractToolId(this.toolReference);
|
||||
|
||||
// 2. 通过协议系统加载工具
|
||||
const toolResult = await this.resourceManager.loadResource(this.toolReference);
|
||||
if (!toolResult.success) {
|
||||
// 调试:尝试不同的查找方式
|
||||
console.log(`🔍 调试:尝试查找工具 ${this.toolReference}`);
|
||||
const directLookup = this.resourceManager.registryData.findResourceById(`tool:${this.toolId}`, 'tool');
|
||||
console.log(` - 直接查找 tool:${this.toolId}: ${directLookup ? '找到' : '未找到'}`);
|
||||
|
||||
throw new Error(`Failed to load tool: ${toolResult.error.message}`);
|
||||
}
|
||||
|
||||
this.toolContent = toolResult.content;
|
||||
|
||||
// 3. 设置沙箱路径
|
||||
this.sandboxPath = await this.resolveSandboxPath();
|
||||
|
||||
// 4. 在基础沙箱中分析工具
|
||||
await this.analyzeToolInSandbox();
|
||||
|
||||
this.isAnalyzed = true;
|
||||
return this.getAnalysisResult();
|
||||
|
||||
} catch (error) {
|
||||
throw new Error(`Tool analysis failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 准备依赖:安装依赖,准备执行环境
|
||||
* @returns {Promise<Object>} 准备结果
|
||||
*/
|
||||
async prepareDependencies() {
|
||||
if (!this.isAnalyzed) {
|
||||
await this.analyze();
|
||||
}
|
||||
|
||||
if (this.isPrepared && !this.options.forceReinstall) {
|
||||
return { success: true, message: 'Dependencies already prepared' };
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. 确保沙箱目录存在
|
||||
await this.ensureSandboxDirectory();
|
||||
|
||||
// 2. 如果有依赖,安装它们
|
||||
if (this.dependencies.length > 0) {
|
||||
await this.installDependencies();
|
||||
}
|
||||
|
||||
// 3. 创建执行沙箱环境
|
||||
await this.createExecutionSandbox();
|
||||
|
||||
this.isPrepared = true;
|
||||
return {
|
||||
success: true,
|
||||
sandboxPath: this.sandboxPath,
|
||||
dependencies: this.dependencies
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
throw new Error(`Dependency preparation failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行工具
|
||||
* @param {Object} parameters - 工具参数
|
||||
* @returns {Promise<Object>} 执行结果
|
||||
*/
|
||||
async execute(parameters = {}) {
|
||||
if (!this.isPrepared) {
|
||||
await this.prepareDependencies();
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. 参数验证
|
||||
await this.validateParameters(parameters);
|
||||
|
||||
// 2. 在沙箱中执行工具
|
||||
const result = await this.executeInSandbox(parameters);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
metadata: {
|
||||
toolId: this.toolId,
|
||||
sandboxPath: this.sandboxPath,
|
||||
executionTime: Date.now()
|
||||
}
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
},
|
||||
metadata: {
|
||||
toolId: this.toolId,
|
||||
sandboxPath: this.sandboxPath
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取工具ID
|
||||
* @param {string} toolReference - @tool://url-validator
|
||||
* @returns {string} 工具ID
|
||||
*/
|
||||
extractToolId(toolReference) {
|
||||
const match = toolReference.match(/^@tool:\/\/(.+)$/);
|
||||
if (!match) {
|
||||
throw new Error(`Invalid tool reference format: ${toolReference}`);
|
||||
}
|
||||
return match[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析沙箱路径
|
||||
* @returns {Promise<string>} 沙箱绝对路径
|
||||
*/
|
||||
async resolveSandboxPath() {
|
||||
// 使用 @user://.promptx/toolbox/{toolId} 作为沙箱路径
|
||||
const userDataReference = `@user://.promptx/toolbox/${this.toolId}`;
|
||||
const result = await this.resourceManager.resolveProtocolReference(userDataReference);
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(`Failed to resolve sandbox path: ${result.error}`);
|
||||
}
|
||||
|
||||
// 通过UserProtocol解析实际路径
|
||||
const userProtocol = this.resourceManager.protocols.get('user');
|
||||
const sandboxPath = await userProtocol.resolvePath(
|
||||
`.promptx/toolbox/${this.toolId}`,
|
||||
new Map()
|
||||
);
|
||||
|
||||
return sandboxPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在基础沙箱中分析工具
|
||||
*/
|
||||
async analyzeToolInSandbox() {
|
||||
const basicSandbox = this.createBasicSandbox();
|
||||
const script = new vm.Script(this.toolContent, { filename: `${this.toolId}.js` });
|
||||
const context = vm.createContext(basicSandbox);
|
||||
|
||||
script.runInContext(context);
|
||||
const exported = context.module.exports;
|
||||
|
||||
if (!exported) {
|
||||
throw new Error(`Tool does not export anything: ${this.toolId}`);
|
||||
}
|
||||
|
||||
// 创建工具实例
|
||||
let toolInstance;
|
||||
if (typeof exported === 'function') {
|
||||
toolInstance = new exported();
|
||||
} else if (typeof exported === 'object') {
|
||||
toolInstance = exported;
|
||||
} else {
|
||||
throw new Error(`Invalid tool export format: ${this.toolId}`);
|
||||
}
|
||||
|
||||
// 提取依赖
|
||||
if (typeof toolInstance.getDependencies === 'function') {
|
||||
try {
|
||||
this.dependencies = toolInstance.getDependencies() || [];
|
||||
} catch (error) {
|
||||
console.warn(`[ToolSandbox] Failed to get dependencies for ${this.toolId}: ${error.message}`);
|
||||
this.dependencies = [];
|
||||
}
|
||||
}
|
||||
|
||||
this.toolInstance = toolInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保沙箱目录存在
|
||||
*/
|
||||
async ensureSandboxDirectory() {
|
||||
try {
|
||||
await fs.access(this.sandboxPath);
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
await fs.mkdir(this.sandboxPath, { recursive: true });
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装依赖
|
||||
*/
|
||||
async installDependencies() {
|
||||
if (this.dependencies.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 创建package.json
|
||||
await this.createPackageJson();
|
||||
|
||||
// 2. 使用内置pnpm安装依赖
|
||||
await this.runPnpmInstall();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建package.json
|
||||
*/
|
||||
async createPackageJson() {
|
||||
const packageJsonPath = path.join(this.sandboxPath, 'package.json');
|
||||
|
||||
// 检查是否已存在且不强制重装
|
||||
if (!this.options.forceReinstall) {
|
||||
try {
|
||||
await fs.access(packageJsonPath);
|
||||
return; // 已存在,跳过
|
||||
} catch (error) {
|
||||
// 不存在,继续创建
|
||||
}
|
||||
}
|
||||
|
||||
const packageJson = {
|
||||
name: `toolbox-${this.toolId}`,
|
||||
version: '1.0.0',
|
||||
description: `Sandbox for tool: ${this.toolId}`,
|
||||
private: true,
|
||||
dependencies: {}
|
||||
};
|
||||
|
||||
// 解析依赖格式 ["validator@^13.11.0", "lodash"]
|
||||
for (const dep of this.dependencies) {
|
||||
if (dep.includes('@')) {
|
||||
const [name, version] = dep.split('@');
|
||||
packageJson.dependencies[name] = version;
|
||||
} else {
|
||||
packageJson.dependencies[dep] = 'latest';
|
||||
}
|
||||
}
|
||||
|
||||
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行pnpm安装
|
||||
*/
|
||||
async runPnpmInstall() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 获取内置pnpm路径 - 直接从node_modules获取
|
||||
const pnpmModulePath = require.resolve('pnpm');
|
||||
const pnpmBinPath = path.join(path.dirname(pnpmModulePath), 'bin', 'pnpm.cjs');
|
||||
|
||||
const pnpm = spawn('node', [pnpmBinPath, 'install'], {
|
||||
cwd: this.sandboxPath,
|
||||
stdio: 'pipe'
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
pnpm.stdout.on('data', (data) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
|
||||
pnpm.stderr.on('data', (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
|
||||
pnpm.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
resolve({ stdout, stderr });
|
||||
} else {
|
||||
reject(new Error(`pnpm install failed with code ${code}: ${stderr}`));
|
||||
}
|
||||
});
|
||||
|
||||
pnpm.on('error', (error) => {
|
||||
reject(new Error(`Failed to spawn pnpm: ${error.message}`));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建执行沙箱环境
|
||||
*/
|
||||
async createExecutionSandbox() {
|
||||
this.sandboxContext = this.createSmartSandbox();
|
||||
|
||||
// 在智能沙箱中重新加载工具
|
||||
const script = new vm.Script(this.toolContent, { filename: `${this.toolId}.js` });
|
||||
const context = vm.createContext(this.sandboxContext);
|
||||
|
||||
script.runInContext(context);
|
||||
const exported = context.module.exports;
|
||||
|
||||
if (typeof exported === 'function') {
|
||||
this.toolInstance = new exported();
|
||||
} else if (typeof exported === 'object') {
|
||||
this.toolInstance = exported;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建基础沙箱
|
||||
*/
|
||||
createBasicSandbox() {
|
||||
return {
|
||||
require: require,
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
console: console,
|
||||
Buffer: Buffer,
|
||||
process: {
|
||||
env: process.env,
|
||||
hrtime: process.hrtime
|
||||
},
|
||||
setTimeout: setTimeout,
|
||||
clearTimeout: clearTimeout,
|
||||
setInterval: setInterval,
|
||||
clearInterval: clearInterval,
|
||||
Object: Object,
|
||||
Array: Array,
|
||||
String: String,
|
||||
Number: Number,
|
||||
Boolean: Boolean,
|
||||
Date: Date,
|
||||
JSON: JSON,
|
||||
Math: Math,
|
||||
RegExp: RegExp,
|
||||
Error: Error,
|
||||
URL: URL
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建智能沙箱(支持依赖)
|
||||
*/
|
||||
createSmartSandbox() {
|
||||
return {
|
||||
require: (moduleName) => {
|
||||
try {
|
||||
// 优先从沙箱目录查找依赖
|
||||
return require(require.resolve(moduleName, {
|
||||
paths: [
|
||||
path.join(this.sandboxPath, 'node_modules'),
|
||||
this.sandboxPath,
|
||||
process.cwd() + '/node_modules'
|
||||
]
|
||||
}));
|
||||
} catch (error) {
|
||||
// 回退到默认require
|
||||
return require(moduleName);
|
||||
}
|
||||
},
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
console: console,
|
||||
Buffer: Buffer,
|
||||
process: {
|
||||
env: process.env,
|
||||
hrtime: process.hrtime
|
||||
},
|
||||
setTimeout: setTimeout,
|
||||
clearTimeout: clearTimeout,
|
||||
setInterval: setInterval,
|
||||
clearInterval: clearInterval,
|
||||
Object: Object,
|
||||
Array: Array,
|
||||
String: String,
|
||||
Number: Number,
|
||||
Boolean: Boolean,
|
||||
Date: Date,
|
||||
JSON: JSON,
|
||||
Math: Math,
|
||||
RegExp: RegExp,
|
||||
Error: Error,
|
||||
URL: URL
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数验证
|
||||
*/
|
||||
async validateParameters(parameters) {
|
||||
if (typeof this.toolInstance.validate === 'function') {
|
||||
const result = this.toolInstance.validate(parameters);
|
||||
|
||||
if (typeof result === 'boolean' && !result) {
|
||||
throw new Error('Parameter validation failed');
|
||||
} else if (result && typeof result === 'object' && !result.valid) {
|
||||
throw new Error(`Parameter validation failed: ${result.errors?.join(', ')}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在沙箱中执行工具
|
||||
*/
|
||||
async executeInSandbox(parameters) {
|
||||
if (!this.toolInstance || typeof this.toolInstance.execute !== 'function') {
|
||||
throw new Error(`Tool ${this.toolId} does not have execute method`);
|
||||
}
|
||||
|
||||
return await this.toolInstance.execute(parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分析结果
|
||||
*/
|
||||
getAnalysisResult() {
|
||||
return {
|
||||
toolId: this.toolId,
|
||||
dependencies: this.dependencies,
|
||||
sandboxPath: this.sandboxPath,
|
||||
hasMetadata: typeof this.toolInstance?.getMetadata === 'function',
|
||||
hasSchema: typeof this.toolInstance?.getSchema === 'function'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理沙箱资源
|
||||
*/
|
||||
async cleanup() {
|
||||
// 可选:清理临时文件、关闭连接等
|
||||
this.sandboxContext = null;
|
||||
this.toolInstance = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具元信息
|
||||
*/
|
||||
getToolMetadata() {
|
||||
if (this.toolInstance && typeof this.toolInstance.getMetadata === 'function') {
|
||||
return this.toolInstance.getMetadata();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具Schema
|
||||
*/
|
||||
getToolSchema() {
|
||||
if (this.toolInstance && typeof this.toolInstance.getSchema === 'function') {
|
||||
return this.toolInstance.getSchema();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ToolSandbox;
|
||||
Reference in New Issue
Block a user