/** * skill-config-loader.js — 从 SQLite skills DB 生成统一的运行时配置 * * 替代 agents.json,所有 agent/skill 注册统一由 skill-db.js (SQLite) 管理。 * 生成与 agents.json 完全一致的 config 结构,下游代码零修改。 * * config = { * agents: { [name]: { type, protocol, transport, launcher, client, prompt, description } }, * agentGroups: { [name]: { name, description, agents, ... } }, * settings: { maxIterations, timeout, logsDir }, * clients: { ... } // 由 backends.json 提供 * } */ const fs = require('fs'); const path = require('path'); /** * 从文件系统动态加载 Skill 配置(无需 SQLite) * @param {string} skillsCodeDir - 例如 data/skills/ * @param {object} [backendsConfig] - backends.json */ function loadConfigFromSkills(skillsCodeDir, backendsConfig = {}) { // 扫描所有 Agent 专属的技能挂载目录 (符合 Local File-System First) const dirsToScan = []; const agentsRoot = require('../../services/agent-locator').AGENTS_ROOT; if (fs.existsSync(agentsRoot)) { const agentFolders = fs.readdirSync(agentsRoot, { withFileTypes: true }); for (const f of agentFolders) { if (f.isDirectory() && !f.name.startsWith('.')) { const claudeSkillsPath = path.join(agentsRoot, f.name, '.claude', 'skills'); if (fs.existsSync(claudeSkillsPath)) { dirsToScan.push({ base: claudeSkillsPath, isCore: true }); } } } } const agents = {}; const groups = {}; const settings = {}; for (const scan of dirsToScan) { if (!fs.existsSync(scan.base)) continue; const entries = fs.readdirSync(scan.base, { withFileTypes: true }); for (const entry of entries) { if (!entry.isDirectory() || entry.name.startsWith('.')) continue; const name = entry.name; let type = 'executor'; let description = ''; const skillDir = path.join(scan.base, name); const promptPath = path.join(skillDir, 'SKILL.md'); // 尝试读取 SKILL.md 或 metadata.json 获取基本信息 let isOrchestator = false; if (fs.existsSync(promptPath)) { const content = fs.readFileSync(promptPath, 'utf-8'); const descMatch = content.match(/description:\s*(?:>|\|)?\s*(.+)/); if (descMatch) description = descMatch[1].trim(); // 简单的探查:如果技能本身是大脑编排器 if (content.includes('layer: orchestrator') || content.includes('Orchestrator') || content.includes('Master')) { isOrchestator = name === 'master' || name === 'innovator'; } } if (isOrchestator) { agents[name] = { type: 'orchestrator', agentGroup: name, description }; } else { agents[name] = { type, protocol: 'claude-cli', transport: 'pipe', launcher: 'claude-local', client: 'claude-local', prompt: promptPath, description }; } } } // --- 合并 backends 配置 --- const config = { ...backendsConfig, agents, agentGroups: groups, settings: { maxIterations: 10, timeout: 600000, logsDir: '.autodev-logs', ...settings, }, }; // 桥接 launchers → clients(与原 master-chat.js 逻辑一致) if (config.launchers && !config.clients) { config.clients = {}; for (const [name, launcher] of Object.entries(config.launchers)) { config.clients[name] = { name, command: launcher.command, acpArgs: launcher.args || [], acpProtocol: launcher.protocol || 'acp', }; } } // 桥接 launcher → client for (const agent of Object.values(config.agents)) { if (agent.launcher && !agent.client) { agent.client = agent.launcher; } } return config; } module.exports = { loadConfigFromSkills };