claude-autopm 1.30.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/autopm/.claude/mcp/test-server.md +10 -0
- package/autopm/.claude/scripts/github/dependency-tracker.js +554 -0
- package/autopm/.claude/scripts/github/dependency-validator.js +545 -0
- package/autopm/.claude/scripts/github/dependency-visualizer.js +477 -0
- package/autopm/.claude/scripts/pm/lib/epic-discovery.js +119 -0
- package/autopm/.claude/scripts/pm/next.js +56 -58
- package/bin/autopm-poc.js +348 -0
- package/bin/autopm.js +6 -0
- package/lib/ai-providers/AbstractAIProvider.js +524 -0
- package/lib/ai-providers/ClaudeProvider.js +423 -0
- package/lib/ai-providers/TemplateProvider.js +432 -0
- package/lib/cli/commands/agent.js +206 -0
- package/lib/cli/commands/config.js +488 -0
- package/lib/cli/commands/prd.js +345 -0
- package/lib/cli/commands/task.js +206 -0
- package/lib/config/ConfigManager.js +531 -0
- package/lib/errors/AIProviderError.js +164 -0
- package/lib/services/AgentService.js +557 -0
- package/lib/services/EpicService.js +609 -0
- package/lib/services/PRDService.js +1003 -0
- package/lib/services/TaskService.js +760 -0
- package/lib/services/interfaces.js +753 -0
- package/lib/utils/CircuitBreaker.js +165 -0
- package/lib/utils/Encryption.js +201 -0
- package/lib/utils/RateLimiter.js +241 -0
- package/lib/utils/ServiceFactory.js +165 -0
- package/package.json +9 -5
- package/scripts/config/get.js +108 -0
- package/scripts/config/init.js +100 -0
- package/scripts/config/list-providers.js +93 -0
- package/scripts/config/set-api-key.js +107 -0
- package/scripts/config/set-provider.js +201 -0
- package/scripts/config/set.js +139 -0
- package/scripts/config/show.js +181 -0
- package/autopm/.claude/.env +0 -158
- package/autopm/.claude/settings.local.json +0 -9
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentService - Standalone Agent Invocation System
|
|
3
|
+
*
|
|
4
|
+
* Provides programmatic access to ClaudeAutoPM agents without requiring
|
|
5
|
+
* the full framework. Enables agent invocation with AI provider abstraction.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Load and parse agent metadata from .md files
|
|
9
|
+
* - Invoke agents with structured prompts (XML-based per Anthropic best practices)
|
|
10
|
+
* - Stream agent responses with AsyncGenerator
|
|
11
|
+
* - List and search available agents
|
|
12
|
+
* - Multi-turn conversations with context persistence
|
|
13
|
+
*
|
|
14
|
+
* Architecture:
|
|
15
|
+
* - Uses AbstractAIProvider interface for AI provider abstraction
|
|
16
|
+
* - Follows Anthropic prompt engineering best practices (XML tags, chain-of-thought)
|
|
17
|
+
* - Parses agent markdown to extract specialization, tools, documentation queries
|
|
18
|
+
* - Injects Context7 queries into system prompts
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* const service = new AgentService(aiProvider);
|
|
22
|
+
* const result = await service.invoke('code-analyzer', 'Review security', context);
|
|
23
|
+
*
|
|
24
|
+
* @class AgentService
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const fs = require('fs').promises;
|
|
28
|
+
const path = require('path');
|
|
29
|
+
|
|
30
|
+
class AgentService {
|
|
31
|
+
/**
|
|
32
|
+
* Create an AgentService instance
|
|
33
|
+
* @param {AbstractAIProvider} aiProvider - AI provider instance for completions
|
|
34
|
+
*/
|
|
35
|
+
constructor(aiProvider) {
|
|
36
|
+
if (!aiProvider) {
|
|
37
|
+
throw new Error('AgentService requires an AI provider instance');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.aiProvider = aiProvider;
|
|
41
|
+
this.agentsBaseDir = path.join(process.cwd(), 'autopm/.claude/agents');
|
|
42
|
+
this.conversationHistories = new Map();
|
|
43
|
+
|
|
44
|
+
// Cache for loaded agents (performance optimization)
|
|
45
|
+
this._agentCache = new Map();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Load agent metadata from .md file
|
|
50
|
+
*
|
|
51
|
+
* Searches for agent in subdirectories (core/, cloud/, devops/, etc.)
|
|
52
|
+
* and parses the markdown content to extract metadata.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} agentName - Name of the agent (without .md extension)
|
|
55
|
+
* @returns {Promise<Object>} Agent metadata
|
|
56
|
+
* @throws {Error} If agent not found or failed to parse
|
|
57
|
+
*/
|
|
58
|
+
async loadAgent(agentName) {
|
|
59
|
+
// Check cache first
|
|
60
|
+
if (this._agentCache.has(agentName)) {
|
|
61
|
+
return this._agentCache.get(agentName);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
// Find agent file in subdirectories
|
|
66
|
+
const agentPath = await this._findAgentFile(agentName);
|
|
67
|
+
|
|
68
|
+
if (!agentPath) {
|
|
69
|
+
throw new Error(`Agent not found: ${agentName}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Read and parse agent file
|
|
73
|
+
const content = await fs.readFile(agentPath, 'utf-8');
|
|
74
|
+
const metadata = this.parseAgent(content);
|
|
75
|
+
|
|
76
|
+
// Validate that agent has required fields
|
|
77
|
+
if (!metadata.title) {
|
|
78
|
+
throw new Error(`Agent ${agentName} is missing required field: title`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Add name and path to metadata
|
|
82
|
+
metadata.name = agentName;
|
|
83
|
+
metadata.path = agentPath;
|
|
84
|
+
|
|
85
|
+
// Determine category from path
|
|
86
|
+
metadata.category = this._extractCategory(agentPath);
|
|
87
|
+
|
|
88
|
+
// Cache the result
|
|
89
|
+
this._agentCache.set(agentName, metadata);
|
|
90
|
+
|
|
91
|
+
return metadata;
|
|
92
|
+
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (error.message.includes('Agent not found')) {
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Wrap parsing errors
|
|
99
|
+
throw new Error(`Failed to parse agent ${agentName}: ${error.message}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Parse agent markdown content
|
|
105
|
+
*
|
|
106
|
+
* Extracts:
|
|
107
|
+
* - Title (# heading)
|
|
108
|
+
* - Specialization (**Specialization:** line)
|
|
109
|
+
* - Documentation Queries (mcp:// links)
|
|
110
|
+
* - Methodologies (**Methodologies:** section)
|
|
111
|
+
* - Tools (**Tools:** line)
|
|
112
|
+
*
|
|
113
|
+
* @param {string} markdownContent - Raw markdown content
|
|
114
|
+
* @returns {Object} Parsed agent metadata
|
|
115
|
+
*/
|
|
116
|
+
parseAgent(markdownContent) {
|
|
117
|
+
const metadata = {
|
|
118
|
+
title: '',
|
|
119
|
+
specialization: '',
|
|
120
|
+
documentationQueries: [],
|
|
121
|
+
methodologies: [],
|
|
122
|
+
tools: '',
|
|
123
|
+
rawContent: markdownContent
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Split into lines for parsing
|
|
127
|
+
const lines = markdownContent.split('\n');
|
|
128
|
+
|
|
129
|
+
let inDocQueriesSection = false;
|
|
130
|
+
let inMethodologiesSection = false;
|
|
131
|
+
|
|
132
|
+
for (let i = 0; i < lines.length; i++) {
|
|
133
|
+
const line = lines[i].trim();
|
|
134
|
+
|
|
135
|
+
// Extract title from # heading
|
|
136
|
+
if (line.startsWith('# ') && !metadata.title) {
|
|
137
|
+
metadata.title = line.substring(2).trim();
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Extract specialization
|
|
142
|
+
if (line.startsWith('**Specialization:**')) {
|
|
143
|
+
metadata.specialization = line
|
|
144
|
+
.substring('**Specialization:**'.length)
|
|
145
|
+
.trim();
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Start of Documentation Queries section
|
|
150
|
+
if (line.startsWith('**Documentation Queries:**')) {
|
|
151
|
+
inDocQueriesSection = true;
|
|
152
|
+
inMethodologiesSection = false;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Start of Methodologies section
|
|
157
|
+
if (line.startsWith('**Methodologies:**')) {
|
|
158
|
+
inMethodologiesSection = true;
|
|
159
|
+
inDocQueriesSection = false;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Extract tools
|
|
164
|
+
if (line.startsWith('**Tools:**')) {
|
|
165
|
+
metadata.tools = line
|
|
166
|
+
.substring('**Tools:**'.length)
|
|
167
|
+
.trim();
|
|
168
|
+
inDocQueriesSection = false;
|
|
169
|
+
inMethodologiesSection = false;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Parse Documentation Queries
|
|
174
|
+
if (inDocQueriesSection && line.startsWith('-')) {
|
|
175
|
+
// Extract the full line (query and description)
|
|
176
|
+
const query = line.substring(1).trim();
|
|
177
|
+
if (query) {
|
|
178
|
+
metadata.documentationQueries.push(query);
|
|
179
|
+
}
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Parse Methodologies
|
|
184
|
+
if (inMethodologiesSection && line.startsWith('-')) {
|
|
185
|
+
const methodology = line.substring(1).trim();
|
|
186
|
+
if (methodology) {
|
|
187
|
+
metadata.methodologies.push(methodology);
|
|
188
|
+
}
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// End sections if we hit another ** heading
|
|
193
|
+
if (line.startsWith('**') && !line.startsWith('**Documentation Queries:**') && !line.startsWith('**Methodologies:**')) {
|
|
194
|
+
inDocQueriesSection = false;
|
|
195
|
+
inMethodologiesSection = false;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return metadata;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Invoke an agent with a task and context
|
|
204
|
+
*
|
|
205
|
+
* Loads the agent, builds structured prompts with XML tags,
|
|
206
|
+
* and calls the AI provider's complete() method.
|
|
207
|
+
*
|
|
208
|
+
* @param {string} agentName - Name of the agent to invoke
|
|
209
|
+
* @param {string} task - Task description for the agent
|
|
210
|
+
* @param {Object} context - Additional context (will be JSON stringified)
|
|
211
|
+
* @param {Object} options - Options
|
|
212
|
+
* @param {string} [options.conversationId] - ID for multi-turn conversations
|
|
213
|
+
* @param {number} [options.maxTokens] - Override max tokens
|
|
214
|
+
* @param {number} [options.temperature] - Override temperature
|
|
215
|
+
* @returns {Promise<string>} Agent response
|
|
216
|
+
* @throws {Error} If agent not found or invocation fails
|
|
217
|
+
*/
|
|
218
|
+
async invoke(agentName, task, context = {}, options = {}) {
|
|
219
|
+
try {
|
|
220
|
+
// Load agent metadata
|
|
221
|
+
const agentMetadata = await this.loadAgent(agentName);
|
|
222
|
+
|
|
223
|
+
// Build system prompt with agent role and Context7 queries
|
|
224
|
+
const systemPrompt = this._buildSystemPrompt(agentMetadata);
|
|
225
|
+
|
|
226
|
+
// Build user prompt with XML-structured task and context
|
|
227
|
+
const userPrompt = this._buildUserPrompt(task, context, agentName);
|
|
228
|
+
|
|
229
|
+
// Track conversation history if conversationId provided
|
|
230
|
+
let conversationHistory = null;
|
|
231
|
+
if (options.conversationId) {
|
|
232
|
+
if (!this.conversationHistories.has(options.conversationId)) {
|
|
233
|
+
this.conversationHistories.set(options.conversationId, []);
|
|
234
|
+
}
|
|
235
|
+
conversationHistory = this.conversationHistories.get(options.conversationId);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Call AI provider
|
|
239
|
+
const response = await this.aiProvider.complete(userPrompt, {
|
|
240
|
+
system: systemPrompt,
|
|
241
|
+
conversationId: options.conversationId,
|
|
242
|
+
maxTokens: options.maxTokens,
|
|
243
|
+
temperature: options.temperature
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Store in conversation history
|
|
247
|
+
if (conversationHistory) {
|
|
248
|
+
conversationHistory.push({
|
|
249
|
+
role: 'user',
|
|
250
|
+
content: userPrompt,
|
|
251
|
+
timestamp: Date.now()
|
|
252
|
+
});
|
|
253
|
+
conversationHistory.push({
|
|
254
|
+
role: 'assistant',
|
|
255
|
+
content: response,
|
|
256
|
+
timestamp: Date.now()
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return response;
|
|
261
|
+
|
|
262
|
+
} catch (error) {
|
|
263
|
+
// Check for specific error patterns and provide helpful messages
|
|
264
|
+
if (error.message && error.message.includes('rate limit')) {
|
|
265
|
+
throw new Error(
|
|
266
|
+
`Failed to invoke agent ${agentName}: Rate limit exceeded. ` +
|
|
267
|
+
`Consider using template-based fallback or retry later.`
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (error.message && error.message.includes('RATE_LIMIT_EXCEEDED')) {
|
|
272
|
+
throw new Error(
|
|
273
|
+
`Failed to invoke agent ${agentName}: Rate limit exceeded. ` +
|
|
274
|
+
`Try using template-based fallback for offline operation.`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Generic error with template suggestion
|
|
279
|
+
throw new Error(
|
|
280
|
+
`Failed to invoke agent ${agentName}: ${error.message}. ` +
|
|
281
|
+
`Consider using template-based fallback if AI provider is unavailable.`
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Invoke agent with streaming response
|
|
288
|
+
*
|
|
289
|
+
* Returns an AsyncGenerator that yields chunks as they arrive.
|
|
290
|
+
*
|
|
291
|
+
* @param {string} agentName - Name of the agent to invoke
|
|
292
|
+
* @param {string} task - Task description for the agent
|
|
293
|
+
* @param {Object} context - Additional context
|
|
294
|
+
* @param {Object} options - Options (same as invoke)
|
|
295
|
+
* @returns {AsyncGenerator<string>} Stream of response chunks
|
|
296
|
+
* @throws {Error} If agent not found or streaming fails
|
|
297
|
+
*/
|
|
298
|
+
async *invokeStream(agentName, task, context = {}, options = {}) {
|
|
299
|
+
try {
|
|
300
|
+
// Load agent metadata
|
|
301
|
+
const agentMetadata = await this.loadAgent(agentName);
|
|
302
|
+
|
|
303
|
+
// Build prompts
|
|
304
|
+
const systemPrompt = this._buildSystemPrompt(agentMetadata);
|
|
305
|
+
const userPrompt = this._buildUserPrompt(task, context, agentName);
|
|
306
|
+
|
|
307
|
+
// Stream from AI provider
|
|
308
|
+
for await (const chunk of this.aiProvider.stream(userPrompt, {
|
|
309
|
+
system: systemPrompt,
|
|
310
|
+
conversationId: options.conversationId,
|
|
311
|
+
maxTokens: options.maxTokens,
|
|
312
|
+
temperature: options.temperature
|
|
313
|
+
})) {
|
|
314
|
+
yield chunk;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
} catch (error) {
|
|
318
|
+
throw new Error(`Failed to stream from agent ${agentName}: ${error.message}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* List all available agents
|
|
324
|
+
*
|
|
325
|
+
* Scans the agents directory and returns metadata for all agents.
|
|
326
|
+
*
|
|
327
|
+
* @param {Object} options - Options
|
|
328
|
+
* @param {boolean} [options.grouped] - Group agents by category
|
|
329
|
+
* @returns {Promise<Array|Object>} Array of agents or grouped object
|
|
330
|
+
*/
|
|
331
|
+
async listAgents(options = {}) {
|
|
332
|
+
try {
|
|
333
|
+
const agents = [];
|
|
334
|
+
|
|
335
|
+
// Scan all subdirectories
|
|
336
|
+
const categories = await fs.readdir(this.agentsBaseDir);
|
|
337
|
+
|
|
338
|
+
for (const category of categories) {
|
|
339
|
+
const categoryPath = path.join(this.agentsBaseDir, category);
|
|
340
|
+
|
|
341
|
+
// Skip files (we want directories)
|
|
342
|
+
const stats = await fs.stat(categoryPath);
|
|
343
|
+
if (!stats.isDirectory()) {
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Read agent files in this category
|
|
348
|
+
const files = await fs.readdir(categoryPath);
|
|
349
|
+
|
|
350
|
+
for (const file of files) {
|
|
351
|
+
if (file.endsWith('.md') && file !== 'README.md') {
|
|
352
|
+
const agentName = file.replace('.md', '');
|
|
353
|
+
|
|
354
|
+
try {
|
|
355
|
+
const metadata = await this.loadAgent(agentName);
|
|
356
|
+
agents.push({
|
|
357
|
+
name: metadata.name,
|
|
358
|
+
title: metadata.title,
|
|
359
|
+
specialization: metadata.specialization,
|
|
360
|
+
category: metadata.category,
|
|
361
|
+
tools: metadata.tools
|
|
362
|
+
});
|
|
363
|
+
} catch (error) {
|
|
364
|
+
// Skip agents that fail to parse
|
|
365
|
+
console.warn(`Warning: Failed to load agent ${agentName}: ${error.message}`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Return grouped or flat list
|
|
372
|
+
if (options.grouped) {
|
|
373
|
+
return this._groupAgentsByCategory(agents);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return agents;
|
|
377
|
+
|
|
378
|
+
} catch (error) {
|
|
379
|
+
throw new Error(`Failed to list agents: ${error.message}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Search agents by keyword
|
|
385
|
+
*
|
|
386
|
+
* Searches in agent name, title, and specialization.
|
|
387
|
+
*
|
|
388
|
+
* @param {string} query - Search query (case-insensitive)
|
|
389
|
+
* @returns {Promise<Array>} Matching agents
|
|
390
|
+
*/
|
|
391
|
+
async searchAgents(query) {
|
|
392
|
+
const allAgents = await this.listAgents();
|
|
393
|
+
const lowerQuery = query.toLowerCase();
|
|
394
|
+
|
|
395
|
+
return allAgents.filter(agent => {
|
|
396
|
+
return (
|
|
397
|
+
agent.name.toLowerCase().includes(lowerQuery) ||
|
|
398
|
+
agent.title.toLowerCase().includes(lowerQuery) ||
|
|
399
|
+
agent.specialization.toLowerCase().includes(lowerQuery)
|
|
400
|
+
);
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// ============================================================
|
|
405
|
+
// PRIVATE HELPER METHODS
|
|
406
|
+
// ============================================================
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Find agent file in subdirectories
|
|
410
|
+
* @private
|
|
411
|
+
* @param {string} agentName - Name of agent
|
|
412
|
+
* @returns {Promise<string|null>} Full path to agent file or null
|
|
413
|
+
*/
|
|
414
|
+
async _findAgentFile(agentName) {
|
|
415
|
+
const fileName = `${agentName}.md`;
|
|
416
|
+
|
|
417
|
+
try {
|
|
418
|
+
// Read all subdirectories
|
|
419
|
+
const entries = await fs.readdir(this.agentsBaseDir);
|
|
420
|
+
|
|
421
|
+
for (const entry of entries) {
|
|
422
|
+
const entryPath = path.join(this.agentsBaseDir, entry);
|
|
423
|
+
|
|
424
|
+
// Check if it's a directory
|
|
425
|
+
const stats = await fs.stat(entryPath);
|
|
426
|
+
if (stats.isDirectory()) {
|
|
427
|
+
// Check if agent file exists in this directory
|
|
428
|
+
const agentPath = path.join(entryPath, fileName);
|
|
429
|
+
try {
|
|
430
|
+
await fs.access(agentPath);
|
|
431
|
+
return agentPath;
|
|
432
|
+
} catch {
|
|
433
|
+
// File doesn't exist in this directory, continue
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return null;
|
|
439
|
+
|
|
440
|
+
} catch (error) {
|
|
441
|
+
throw new Error(`Failed to search for agent: ${error.message}`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Extract category from agent path
|
|
447
|
+
* @private
|
|
448
|
+
* @param {string} agentPath - Full path to agent file
|
|
449
|
+
* @returns {string} Category name
|
|
450
|
+
*/
|
|
451
|
+
_extractCategory(agentPath) {
|
|
452
|
+
const parts = agentPath.split(path.sep);
|
|
453
|
+
const agentsIndex = parts.indexOf('agents');
|
|
454
|
+
if (agentsIndex >= 0 && agentsIndex < parts.length - 1) {
|
|
455
|
+
return parts[agentsIndex + 1];
|
|
456
|
+
}
|
|
457
|
+
return 'unknown';
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Build system prompt with agent role and Context7 queries
|
|
462
|
+
* @private
|
|
463
|
+
* @param {Object} agentMetadata - Agent metadata
|
|
464
|
+
* @returns {string} System prompt
|
|
465
|
+
*/
|
|
466
|
+
_buildSystemPrompt(agentMetadata) {
|
|
467
|
+
const { title, specialization, documentationQueries, methodologies } = agentMetadata;
|
|
468
|
+
|
|
469
|
+
let prompt = `<agent>
|
|
470
|
+
You are ${title || 'an AI assistant'}.
|
|
471
|
+
|
|
472
|
+
<specialization>
|
|
473
|
+
${specialization || 'General purpose assistant'}
|
|
474
|
+
</specialization>
|
|
475
|
+
`;
|
|
476
|
+
|
|
477
|
+
// Include Context7 documentation queries if available
|
|
478
|
+
if (documentationQueries && documentationQueries.length > 0) {
|
|
479
|
+
prompt += `
|
|
480
|
+
<documentation>
|
|
481
|
+
Before providing your response, consider consulting these documentation resources:
|
|
482
|
+
|
|
483
|
+
`;
|
|
484
|
+
documentationQueries.forEach(query => {
|
|
485
|
+
prompt += `- ${query}\n`;
|
|
486
|
+
});
|
|
487
|
+
prompt += `</documentation>
|
|
488
|
+
`;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Include methodologies if available
|
|
492
|
+
if (methodologies && methodologies.length > 0) {
|
|
493
|
+
prompt += `
|
|
494
|
+
<methodologies>
|
|
495
|
+
Apply these methodologies in your work:
|
|
496
|
+
`;
|
|
497
|
+
methodologies.forEach(method => {
|
|
498
|
+
prompt += `- ${method}\n`;
|
|
499
|
+
});
|
|
500
|
+
prompt += `</methodologies>
|
|
501
|
+
`;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
prompt += `
|
|
505
|
+
Please analyze the task and context provided below, then provide a thorough and accurate response.
|
|
506
|
+
Use chain-of-thought reasoning when appropriate to explain your approach.
|
|
507
|
+
</agent>`;
|
|
508
|
+
|
|
509
|
+
return prompt;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Build user prompt with XML-structured task and context
|
|
514
|
+
* @private
|
|
515
|
+
* @param {string} task - Task description
|
|
516
|
+
* @param {Object} context - Context object
|
|
517
|
+
* @param {string} agentName - Agent name for reference
|
|
518
|
+
* @returns {string} User prompt
|
|
519
|
+
*/
|
|
520
|
+
_buildUserPrompt(task, context, agentName = '') {
|
|
521
|
+
let prompt = `<task>${task}</task>`;
|
|
522
|
+
|
|
523
|
+
// Add context if provided (compact JSON for parsing compatibility)
|
|
524
|
+
if (context && Object.keys(context).length > 0) {
|
|
525
|
+
prompt += ` <context>${JSON.stringify(context)}</context>`;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Add agent reference (useful for logging/debugging)
|
|
529
|
+
if (agentName) {
|
|
530
|
+
prompt += ` <!-- Agent: ${agentName} -->`;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return prompt;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Group agents by category
|
|
538
|
+
* @private
|
|
539
|
+
* @param {Array} agents - Flat list of agents
|
|
540
|
+
* @returns {Object} Grouped agents
|
|
541
|
+
*/
|
|
542
|
+
_groupAgentsByCategory(agents) {
|
|
543
|
+
const grouped = {};
|
|
544
|
+
|
|
545
|
+
agents.forEach(agent => {
|
|
546
|
+
const category = agent.category || 'unknown';
|
|
547
|
+
if (!grouped[category]) {
|
|
548
|
+
grouped[category] = [];
|
|
549
|
+
}
|
|
550
|
+
grouped[category].push(agent);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
return grouped;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
module.exports = AgentService;
|