/** * DevLoop Tool Registry — loadAllTools() * * 把 server/src/tools/devloop/ 下所有 *-tools.js 加载并按工具 name 聚合到 Map。 * tool-manager / ClaudeCliProtocol 启动 Agent 时通过 toolPolicy 取这里的 ToolDef。 * * 规格: docs/prd/newfunction/_spec/arch-tool-registry.md · §④ */ const path = require('path'); const fs = require('fs'); // 工具模块清单(手动 require,避免动态 require 出错) const TOOL_MODULES = [ './persistence-tools', './doc-tools', './coder-tools', './tester-tools', './integrator-tools', './triager-tools', ]; let cached = null; function loadAllTools() { if (cached) return cached; const registry = new Map(); for (const mod of TOOL_MODULES) { const fullPath = path.join(__dirname, mod + '.js'); if (!fs.existsSync(fullPath)) continue; const tools = require(mod); for (const key of Object.keys(tools)) { const tool = tools[key]; if (tool && typeof tool === 'object' && tool.name && typeof tool.execute === 'function') { if (registry.has(tool.name)) { throw new Error(`Duplicate tool name: ${tool.name} (in ${mod})`); } registry.set(tool.name, tool); } } } cached = registry; return registry; } /** * 列工具名(用于 _registry.json toolPolicy 校验) */ function listToolNames() { return Array.from(loadAllTools().keys()).sort(); } /** * 按 toolPolicy 取一组工具(含 LemonBoss 既有工具是允许的,本函数只过滤 devloop 的) */ function getDevLoopTools(toolPolicy) { const all = loadAllTools(); const out = []; for (const name of toolPolicy) { if (all.has(name)) out.push(all.get(name)); } return out; } /** * 调度:根据 name 找工具,执行(中央调用入口,外部 ACP 调用方使用)。 * - 校验工具存在 * - 校验 input 符合 inputSchema(简化版:只校验 required + type) * - 调 tool.execute(input, ctx) * - 错误标准化 */ async function dispatch(name, input, ctx) { const all = loadAllTools(); const tool = all.get(name); if (!tool) { throw new Error(`Unknown tool: ${name}`); } _validateInput(input, tool.inputSchema, name); return tool.execute(input, ctx); } function _validateInput(input, schema, name) { if (!schema || typeof schema !== 'object') return; if (schema.required && Array.isArray(schema.required)) { for (const k of schema.required) { if (input == null || input[k] === undefined) { const e = new Error(`Tool ${name}: missing required field "${k}"`); e.code = 'TOOL_INPUT_INVALID'; throw e; } } } } function _resetForTesting() { cached = null; } module.exports = { loadAllTools, listToolNames, getDevLoopTools, dispatch, _resetForTesting, TOOL_MODULES, };