skill-config-loader.js
4.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/**
* 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 = path.resolve(__dirname, '../../../data/system/agents');
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 };