fix: 鲁班工具开发体验优化 - 五组件架构升级与智能错误处理 (#116)
* feat: 为promptx_tool增加forceReinstall选项支持 解决工具沙箱缓存机制问题,允许强制重新安装工具依赖。 ## 修改内容 ### 1. 工具定义更新 (toolDefinitions.js) - 增加 context.options.forceReinstall 参数支持 - 增加 context.options.timeout 参数支持 - 完善参数描述,说明context用途 - 移除暂时不需要的role_id和session_id参数 ### 2. 执行逻辑优化 (ToolCommand.js) - 支持从context.options提取执行选项 - 将选项传递给ToolSandbox构造函数 - 增加调试日志输出沙箱选项 - 完善JSDoc注释 ## 解决的问题 - Issue #107: PromptX工具沙箱缓存机制不支持工具集更新 - 鲁班等AI角色在调试工具时遇到的缓存问题 - 工具依赖更新后无法自动重新安装的问题 ## 使用方式 ```javascript // 强制重新安装依赖 { tool_resource: "@tool://tool-name", parameters: { /* 工具参数 */ }, context: { options: { forceReinstall: true } } } ``` 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: 简化context参数,移除暂时不需要的role_id和session_id 按照奥卡姆剃刀原则,移除当前没有实际用途的参数: - 移除 context.role_id - 移除 context.session_id - 保留 context.options 用于执行配置 - 简化API接口,降低复杂度 未来如需要可重新添加这些参数。 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: 简化promptx_tool参数结构,移除context层级 ## 主要改进 ### 1. 扁平化参数结构 - 移除复杂的context嵌套,直接使用顶级参数 - forceReinstall 和 timeout 作为可选的顶级参数 - 遵循MCP协议惯例,降低AI理解成本 ### 2. 参数说明优化 - forceReinstall: 明确默认值为false - 详细说明使用场景:工具开发和调试中的缓存问题 - timeout: 明确默认值为30000ms ### 3. 防止盲目调用 - 增加重要提醒:要求AI先了解工具说明再调用 - 避免大模型在不了解工具的情况下盲目执行 ## 新的调用方式 ## 优势 - 符合MCP预训练共识,降低AI学习成本 - 参数结构简洁直观 - 保持向后兼容性 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: 修正鲁班工具开发提示词 - 更新为五组件架构并增加AI引导机制 - 更新DPML工具标签知识:从四组件升级到五组件架构(增加identity标签) - 在工具开发工作流中增加.tool.md说明书创建步骤 - 添加AI使用提醒机制,强化工具使用前必读原则 - 完善工具标签编写模板,包含完整的五标签结构 - 更新质量检查标准,适配新的标签体系 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * feat: 添加智能require错误过滤机制到ToolSandbox 解决工具开发中的常见问题:analyze阶段require依赖导致的错误 核心改进: - 添加_filterRequireError()私有方法,智能识别MODULE_NOT_FOUND错误 - 通过静态分析提取getDependencies()声明的依赖列表 - 对比缺失模块与声明依赖,区分合法缺失和代码错误 - 支持版本号格式的依赖声明(如axios@^1.6.0) 开发者体验提升: - 可以自然地在文件顶部写require()语句 - 忘记声明依赖时仍会得到明确错误提示 - 降低工具开发的认知负担和学习成本 - 遵循奥卡姆剃刀原则:用技术解决技术问题 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@ -211,7 +211,17 @@ class ToolSandbox {
|
||||
const script = new vm.Script(this.toolContent, { filename: `${this.toolId}.js` });
|
||||
const context = vm.createContext(basicSandbox);
|
||||
|
||||
script.runInContext(context);
|
||||
try {
|
||||
script.runInContext(context);
|
||||
} catch (error) {
|
||||
// 使用智能错误过滤处理require错误
|
||||
const filteredError = this._filterRequireError(error);
|
||||
if (filteredError) {
|
||||
throw filteredError;
|
||||
}
|
||||
// 如果是预期的require错误,继续执行
|
||||
}
|
||||
|
||||
const exported = context.module.exports;
|
||||
|
||||
if (!exported) {
|
||||
@ -241,6 +251,87 @@ class ToolSandbox {
|
||||
this.toolInstance = toolInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能过滤require错误
|
||||
* @param {Error} error - 捕获的错误
|
||||
* @returns {Error|null} - 如果是真正的错误则返回Error对象,如果是预期的require错误则返回null
|
||||
* @private
|
||||
*/
|
||||
_filterRequireError(error) {
|
||||
// 检查是否是MODULE_NOT_FOUND错误
|
||||
if (error.code === 'MODULE_NOT_FOUND') {
|
||||
const missingModule = this._extractMissingModuleName(error.message);
|
||||
|
||||
if (missingModule) {
|
||||
// 获取已声明的依赖列表
|
||||
const declaredDependencies = this._extractDeclaredDependencies();
|
||||
|
||||
// 检查缺失的模块是否在依赖声明中
|
||||
if (this._isDeclaredInDependencies(missingModule, declaredDependencies)) {
|
||||
console.log(`[ToolSandbox] 依赖 ${missingModule} 未安装,将在prepareDependencies阶段安装`);
|
||||
return null; // 预期的错误,忽略
|
||||
} else {
|
||||
return new Error(`未声明的依赖: ${missingModule},请在getDependencies()中添加此依赖`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 其他错误直接返回
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从错误信息中提取缺失的模块名
|
||||
* @param {string} errorMessage - 错误信息
|
||||
* @returns {string|null} - 模块名或null
|
||||
* @private
|
||||
*/
|
||||
_extractMissingModuleName(errorMessage) {
|
||||
// 匹配 "Cannot find module 'moduleName'" 或 "Cannot resolve module 'moduleName'"
|
||||
const match = errorMessage.match(/Cannot (?:find|resolve) module ['"]([^'"]+)['"]/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试从工具代码中提取已声明的依赖
|
||||
* @returns {string[]} - 依赖列表
|
||||
* @private
|
||||
*/
|
||||
_extractDeclaredDependencies() {
|
||||
try {
|
||||
// 尝试通过正则表达式从代码中提取getDependencies的返回值
|
||||
const dependencyMatch = this.toolContent.match(/getDependencies\s*\(\s*\)\s*\{[\s\S]*?return\s*\[([\s\S]*?)\]/);
|
||||
|
||||
if (dependencyMatch) {
|
||||
const dependencyString = dependencyMatch[1];
|
||||
// 提取字符串字面量
|
||||
const stringMatches = dependencyString.match(/['"]([^'"]+)['"]/g);
|
||||
if (stringMatches) {
|
||||
return stringMatches.map(str => str.slice(1, -1)); // 去掉引号
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`[ToolSandbox] 无法解析依赖声明: ${error.message}`);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块是否在依赖声明中
|
||||
* @param {string} moduleName - 模块名
|
||||
* @param {string[]} declaredDependencies - 已声明的依赖列表
|
||||
* @returns {boolean} - 是否已声明
|
||||
* @private
|
||||
*/
|
||||
_isDeclaredInDependencies(moduleName, declaredDependencies) {
|
||||
return declaredDependencies.some(dep => {
|
||||
// 支持 "axios@^1.6.0" 格式,提取模块名部分
|
||||
const depName = dep.split('@')[0];
|
||||
return depName === moduleName;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保沙箱目录存在
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user