marketplace-tools.js
6.66 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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
/**
* marketplace-tools.js — 市场技能搜索 + 动态调用工具
*
* 为 Master Agent 提供搜索和动态调用 27,000+ 市场技能的能力。
* 核心系统 agent 直接列在 prompt 中;市场技能通过 搜索 → 选择 → 调用 的方式使用。
*/
const fs = require('fs');
const path = require('path');
/**
* search_marketplace_skills — 搜索市场技能
*/
function createSearchSkillsTool() {
return {
name: 'search_marketplace_skills',
description: `在 ArenaSK 市场中搜索技能(27,000+ 可用)。返回匹配的技能列表(名称、描述、ELO 评分等)。
使用场景:
- 用户需要某个特定领域的技能时,先搜索找到最合适的
- 不确定是否存在某个技能时,先搜索确认
- 需要对比多个同类技能时
搜索后,如果找到合适的技能,可以用 call_marketplace_skill 工具动态调用它。`,
parameters: {
type: 'object',
required: ['query'],
properties: {
query: {
type: 'string',
description: '搜索关键词(支持技能名、描述、标签等)',
},
limit: {
type: 'number',
description: '返回结果数量上限(默认 10,最大 20)',
},
},
},
async execute(params) {
const { query, limit = 10 } = params;
const skillDb = require('../../services/skill-db');
const maxLimit = Math.min(limit || 10, 20);
const results = skillDb.searchSkills(query, { limit: maxLimit });
return {
query,
count: results.length,
skills: results.map(s => ({
name: s.skillName,
description: (s.description || '').substring(0, 200),
domain: s.domain || '',
keywords: s.keywords || '',
downloadCount: s.downloadCount || 0,
})),
hint: results.length > 0
? '找到匹配技能。使用 call_marketplace_skill 工具可以动态调用其中一个。'
: '未找到匹配技能。尝试不同的搜索关键词。',
};
},
};
}
/**
* call_marketplace_skill — 动态调用市场技能
*/
function createCallMarketplaceSkillTool() {
return {
name: 'call_marketplace_skill',
description: `动态调用一个市场技能(先用 search_marketplace_skills 搜索找到技能名,再用此工具调用)。
系统会自动加载该技能的 SKILL.md,创建临时 Claude CLI session 执行任务。
使用流程:
1. 先用 search_marketplace_skills 搜索技能
2. 从搜索结果中选择合适的技能
3. 用此工具调用该技能,传入任务描述`,
parameters: {
type: 'object',
required: ['skillName', 'task'],
properties: {
skillName: {
type: 'string',
description: '要调用的技能名称(从搜索结果中获取)',
},
task: {
type: 'string',
description: '给该技能的任务描述 / 用户输入',
},
},
},
async execute(params, context) {
const { skillName, task } = params;
const { agentManager, onSubAgentData } = context;
const skillDb = require('../../services/skill-db');
const { resolveSkillDir } = require('../../services/skill-dir-manager');
// 1. 验证技能存在
const skill = skillDb.getSkill(skillName);
if (!skill) {
throw new Error(`技能 "${skillName}" 不存在。请先用 search_marketplace_skills 搜索。`);
}
// 2. 查找 SKILL.md
const skillDir = resolveSkillDir(skillName) || skill.directory;
if (!skillDir) {
throw new Error(`技能 "${skillName}" 的目录未找到。`);
}
const skillMdPath = path.join(skillDir, 'SKILL.md');
if (!fs.existsSync(skillMdPath)) {
throw new Error(`技能 "${skillName}" 的 SKILL.md 文件不存在: ${skillMdPath}`);
}
// 3. 读取 SKILL.md 作为 system prompt
const skillMd = fs.readFileSync(skillMdPath, 'utf-8');
// 限制 SKILL.md 大小(避免超大文件)
const MAX_SKILL_SIZE = 100000; // 100KB
if (skillMd.length > MAX_SKILL_SIZE) {
throw new Error(`技能 "${skillName}" 的 SKILL.md 太大 (${skillMd.length} chars),超过 ${MAX_SKILL_SIZE} 限制。`);
}
// 4. 构建完整 prompt: SKILL.md + 用户任务
const fullPrompt = `${skillMd}\n\n---\n\n## 用户任务\n\n${task}`;
// 5. 检查 agentManager 中是否已有该 agent 配置
// 如果没有,临时注册到 config 中
if (!agentManager) {
throw new Error('call_marketplace_skill 需要 agentManager');
}
let agentName = skillName;
try {
agentManager.getAgentConfig(agentName);
} catch {
// 临时注册该 agent 到 config
const config = context.config;
if (config && config.agents) {
config.agents[agentName] = {
type: 'executor',
protocol: 'claude-cli',
transport: 'pipe',
launcher: 'claude-local',
client: 'claude-local',
prompt: skillMdPath,
description: skill.description || '',
};
}
}
// 6. 调用 agent
try {
const result = await agentManager.callAgent(agentName, fullPrompt, {
onData: (chunk) => {
if (onSubAgentData && chunk && chunk.trim()) {
onSubAgentData(agentName, chunk);
}
},
});
return {
skillName,
status: 'success',
output: result.output || '',
};
} catch (err) {
return {
skillName,
status: 'error',
error: err.message,
};
}
},
};
}
module.exports = { createSearchSkillsTool, createCallMarketplaceSkillTool };