agent updates
This commit is contained in:
160
tools/cli/lib/activation-builder.js
Normal file
160
tools/cli/lib/activation-builder.js
Normal file
@@ -0,0 +1,160 @@
|
||||
const fs = require('fs-extra');
|
||||
const path = require('node:path');
|
||||
const { getSourcePath } = require('./project-root');
|
||||
|
||||
/**
|
||||
* Builds activation blocks from fragments based on agent profile
|
||||
*/
|
||||
class ActivationBuilder {
|
||||
constructor() {
|
||||
this.fragmentsDir = getSourcePath('utility', 'models', 'fragments');
|
||||
this.fragmentCache = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a fragment file
|
||||
* @param {string} fragmentName - Name of fragment file (e.g., 'activation-init.xml')
|
||||
* @returns {string} Fragment content
|
||||
*/
|
||||
async loadFragment(fragmentName) {
|
||||
// Check cache first
|
||||
if (this.fragmentCache.has(fragmentName)) {
|
||||
return this.fragmentCache.get(fragmentName);
|
||||
}
|
||||
|
||||
const fragmentPath = path.join(this.fragmentsDir, fragmentName);
|
||||
|
||||
if (!(await fs.pathExists(fragmentPath))) {
|
||||
throw new Error(`Fragment not found: ${fragmentName}`);
|
||||
}
|
||||
|
||||
const content = await fs.readFile(fragmentPath, 'utf8');
|
||||
this.fragmentCache.set(fragmentName, content);
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build complete activation block based on agent profile
|
||||
* @param {Object} profile - Agent profile from AgentAnalyzer
|
||||
* @param {Object} metadata - Agent metadata (module, name, etc.)
|
||||
* @param {Array} agentSpecificActions - Optional agent-specific critical actions
|
||||
* @returns {string} Complete activation block XML
|
||||
*/
|
||||
async buildActivation(profile, metadata = {}, agentSpecificActions = []) {
|
||||
let activation = '<activation critical="MANDATORY">\n';
|
||||
|
||||
// 1. Build sequential steps
|
||||
const steps = await this.buildSteps(metadata, agentSpecificActions);
|
||||
activation += this.indent(steps, 2) + '\n';
|
||||
|
||||
// 2. Build menu handlers section with dynamic handlers
|
||||
const menuHandlers = await this.loadFragment('menu-handlers.xml');
|
||||
|
||||
// Build extract list (comma-separated list of used attributes)
|
||||
const extractList = profile.usedAttributes.join(', ');
|
||||
|
||||
// Build handlers (load only needed handlers)
|
||||
const handlers = await this.buildHandlers(profile);
|
||||
|
||||
const processedHandlers = menuHandlers.replace('{DYNAMIC_EXTRACT_LIST}', extractList).replace('{DYNAMIC_HANDLERS}', handlers);
|
||||
|
||||
activation += '\n' + this.indent(processedHandlers, 2) + '\n';
|
||||
|
||||
// 3. Always include rules
|
||||
const rules = await this.loadFragment('activation-rules.xml');
|
||||
activation += this.indent(rules, 2) + '\n';
|
||||
|
||||
activation += '</activation>';
|
||||
|
||||
return activation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build handlers section based on profile
|
||||
* @param {Object} profile - Agent profile
|
||||
* @returns {string} Handlers XML
|
||||
*/
|
||||
async buildHandlers(profile) {
|
||||
const handlerFragments = [];
|
||||
|
||||
for (const attrType of profile.usedAttributes) {
|
||||
const fragmentName = `handler-${attrType}.xml`;
|
||||
try {
|
||||
const handler = await this.loadFragment(fragmentName);
|
||||
handlerFragments.push(handler);
|
||||
} catch {
|
||||
console.warn(`Warning: Handler fragment not found: ${fragmentName}`);
|
||||
}
|
||||
}
|
||||
|
||||
return handlerFragments.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Build sequential activation steps
|
||||
* @param {Object} metadata - Agent metadata
|
||||
* @param {Array} agentSpecificActions - Optional agent-specific actions
|
||||
* @returns {string} Steps XML
|
||||
*/
|
||||
async buildSteps(metadata = {}, agentSpecificActions = []) {
|
||||
const stepsTemplate = await this.loadFragment('activation-steps.xml');
|
||||
|
||||
// Extract basename from agent ID (e.g., "bmad/bmm/agents/pm.md" → "pm")
|
||||
const agentBasename = metadata.id ? metadata.id.split('/').pop().replace('.md', '') : metadata.name || 'agent';
|
||||
|
||||
// Build agent-specific steps
|
||||
let agentStepsXml = '';
|
||||
let currentStepNum = 4; // Steps 1-3 are standard
|
||||
|
||||
if (agentSpecificActions && agentSpecificActions.length > 0) {
|
||||
agentStepsXml = agentSpecificActions
|
||||
.map((action) => {
|
||||
const step = `<step n="${currentStepNum}">${action}</step>`;
|
||||
currentStepNum++;
|
||||
return step;
|
||||
})
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
// Calculate final step numbers
|
||||
const menuStep = currentStepNum;
|
||||
const haltStep = currentStepNum + 1;
|
||||
const inputStep = currentStepNum + 2;
|
||||
const executeStep = currentStepNum + 3;
|
||||
|
||||
// Replace placeholders
|
||||
const processed = stepsTemplate
|
||||
.replace('{agent-file-basename}', agentBasename)
|
||||
.replace('{module}', metadata.module || 'core')
|
||||
.replace('{AGENT_SPECIFIC_STEPS}', agentStepsXml)
|
||||
.replace('{MENU_STEP}', menuStep.toString())
|
||||
.replace('{HALT_STEP}', haltStep.toString())
|
||||
.replace('{INPUT_STEP}', inputStep.toString())
|
||||
.replace('{EXECUTE_STEP}', executeStep.toString());
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indent XML content
|
||||
* @param {string} content - Content to indent
|
||||
* @param {number} spaces - Number of spaces to indent
|
||||
* @returns {string} Indented content
|
||||
*/
|
||||
indent(content, spaces) {
|
||||
const indentation = ' '.repeat(spaces);
|
||||
return content
|
||||
.split('\n')
|
||||
.map((line) => (line ? indentation + line : line))
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear fragment cache (useful for testing or hot reload)
|
||||
*/
|
||||
clearCache() {
|
||||
this.fragmentCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { ActivationBuilder };
|
||||
Reference in New Issue
Block a user