bundle agents front matter optimized, along with the orchestrators activation instructions;

This commit is contained in:
Brian Madison
2025-10-03 21:46:53 -05:00
parent 5ac18cb55c
commit 9e8c7f3503
86 changed files with 289 additions and 3227 deletions

View File

@@ -151,6 +151,7 @@ class WebBundler {
// Build agent from YAML (no customize file for web bundles)
const xmlContent = await this.yamlBuilder.buildFromYaml(agentPath, null, {
includeMetadata: false, // Don't include build metadata in web bundles
forWebBundle: true, // Use web-specific activation fragments
});
content = xmlContent;
@@ -415,12 +416,9 @@ class WebBundler {
parts.push('', ' <!-- Shared Dependencies -->', ' <dependencies>');
for (const [id, content] of dependencies) {
// Check if content is already wrapped in a <file> tag
const isWrappedFile = content.trim().startsWith('<file ') && content.trim().endsWith('</file>');
const finalContent = isWrappedFile ? content : this.escapeXmlContent(content);
// All dependencies are now consistently wrapped in <file> elements
// Indent properly (add 4 spaces to each line)
const indentedContent = finalContent
const indentedContent = content
.split('\n')
.map((line) => ' ' + line)
.join('\n');
@@ -550,7 +548,6 @@ class WebBundler {
/src="([^"]+)"/g, // Source paths
/system-prompts="([^"]+)"/g,
/tools="([^"]+)"/g,
/workflows="([^"]+)"/g,
/knowledge="([^"]+)"/g,
/{project-root}\/([^"'\s<>]+)/g,
];
@@ -561,20 +558,31 @@ class WebBundler {
let filePath = match[1];
// Remove {project-root} prefix if present
filePath = filePath.replace(/^{project-root}\//, '');
if (filePath) {
// Skip obvious placeholder/example paths
if (filePath && !filePath.includes('path/to/') && !filePath.includes('example')) {
refs.add(filePath);
}
}
}
// Extract run-workflow references (special handling for workflows)
const workflowPattern = /run-workflow="([^"]+)"/g;
let workflowMatch;
while ((workflowMatch = workflowPattern.exec(xml)) !== null) {
let workflowPath = workflowMatch[1];
workflowPath = workflowPath.replace(/^{project-root}\//, '');
if (workflowPath) {
workflowRefs.add(workflowPath);
// Extract workflow references - both 'workflow' and 'run-workflow' attributes
const workflowPatterns = [
/workflow="([^"]+)"/g, // Menu items with workflow attribute
/run-workflow="([^"]+)"/g, // Commands with run-workflow attribute
/validate-workflow="([^"]+)"/g, // Validation workflow references
];
for (const pattern of workflowPatterns) {
let match;
while ((match = pattern.exec(xml)) !== null) {
let workflowPath = match[1];
workflowPath = workflowPath.replace(/^{project-root}\//, '');
// Skip obvious placeholder/example paths
if (workflowPath && workflowPath.endsWith('.yaml') && !workflowPath.includes('path/to/') && !workflowPath.includes('example')) {
workflowRefs.add(workflowPath);
}
}
}
@@ -612,6 +620,11 @@ class WebBundler {
}
processed.add(filePath);
// Skip agent-party.xml manifest for web bundles (agents are already bundled)
if (filePath === 'bmad/_cfg/agent-party.xml' || filePath.endsWith('/agent-party.xml')) {
return;
}
// Handle wildcard patterns
if (filePath.includes('*')) {
await this.processWildcardDependency(filePath, dependencies, processed, moduleName, warnings);
@@ -737,8 +750,10 @@ class WebBundler {
indexParts.push(' </items>', '</file-index>');
// Store the XML version
dependencies.set(filePath, indexParts.join('\n'));
// Store the XML version wrapped in a file element
const csvXml = indexParts.join('\n');
const wrappedCsv = `<file id="${filePath}" type="xml">\n${csvXml}\n</file>`;
dependencies.set(filePath, wrappedCsv);
// Process referenced files from CSV
for (const refId of referencedFiles) {
@@ -760,8 +775,34 @@ class WebBundler {
}
}
// Store the processed content
dependencies.set(filePath, processedContent);
// Determine file type for wrapping
let fileType = 'text';
if (ext === '.xml' || (ext === '.md' && processedContent.trim().startsWith('<'))) {
fileType = 'xml';
} else
switch (ext) {
case '.yaml':
case '.yml': {
fileType = 'yaml';
break;
}
case '.json': {
fileType = 'json';
break;
}
case '.md': {
fileType = 'md';
break;
}
// No default
}
// Wrap content in file element and store
const wrappedContent = this.wrapContentInXml(processedContent, filePath, fileType);
dependencies.set(filePath, wrappedContent);
// Recursively scan for more dependencies
const { refs: nestedRefs } = this.extractFileReferences(processedContent);
@@ -870,7 +911,7 @@ class WebBundler {
* Include core workflow task files
*/
async includeCoreWorkflowFiles(dependencies, processed, moduleName, warnings = []) {
const coreWorkflowPath = 'bmad/core/tasks/workflow.md';
const coreWorkflowPath = 'bmad/core/tasks/workflow.xml';
if (processed.has(coreWorkflowPath)) {
return;
@@ -885,7 +926,7 @@ class WebBundler {
}
const fileContent = await fs.readFile(actualPath, 'utf8');
const wrappedContent = this.wrapContentInXml(fileContent, coreWorkflowPath, 'md');
const wrappedContent = this.wrapContentInXml(fileContent, coreWorkflowPath, 'xml');
dependencies.set(coreWorkflowPath, wrappedContent);
}
@@ -893,7 +934,7 @@ class WebBundler {
* Include advanced elicitation files
*/
async includeAdvancedElicitationFiles(dependencies, processed, moduleName, warnings = []) {
const elicitationFiles = ['bmad/core/tasks/adv-elicit.md', 'bmad/core/tasks/adv-elicit-methods.csv'];
const elicitationFiles = ['bmad/core/tasks/adv-elicit.xml', 'bmad/core/tasks/adv-elicit-methods.csv'];
for (const filePath of elicitationFiles) {
if (processed.has(filePath)) {
@@ -919,6 +960,14 @@ class WebBundler {
* Wrap file content in XML with proper escaping
*/
wrapContentInXml(content, id, type = 'text') {
// For XML files, include directly without CDATA (they're already valid XML)
if (type === 'xml') {
// XML files can be included directly as they're already well-formed
// Just wrap in a file element
return `<file id="${id}" type="${type}">\n${content}\n</file>`;
}
// For all other file types, use CDATA to preserve content exactly
// Escape any ]]> sequences in the content by splitting CDATA sections
// Replace ]]> with ]]]]><![CDATA[> to properly escape it within CDATA
const escapedContent = content.replaceAll(']]>', ']]]]><![CDATA[>');
@@ -1163,11 +1212,6 @@ class WebBundler {
// First, always inject help/exit menu items
agentXml = this.injectHelpExitMenuItems(agentXml);
// Check if agent already has an activation block
if (agentXml.includes('<activation')) {
return agentXml; // Already has activation, don't inject activation but help/exit was added above
}
// Load the web activation template
const activationPath = path.join(this.sourceDir, 'utility', 'models', 'agent-activation-web.xml');
@@ -1178,8 +1222,17 @@ class WebBundler {
const activationXml = fs.readFileSync(activationPath, 'utf8');
// For web bundles, replace critical-actions with activation
// This is because web bundles can't load config files, so critical-actions are not applicable
// For web bundles, ALWAYS replace existing activation with web activation
// This is because fragment-based activation assumes filesystem access which won't work in web bundles
const hasActivation = agentXml.includes('<activation');
if (hasActivation) {
// Replace existing activation block with web activation
const injectedXml = agentXml.replace(/<activation[^>]*>[\s\S]*?<\/activation>/, activationXml);
return injectedXml;
}
// Check for critical-actions block (legacy)
const hasCriticalActions = agentXml.includes('<critical-actions');
if (hasCriticalActions) {
@@ -1212,8 +1265,8 @@ class WebBundler {
* Build the final agent bundle XML
*/
buildAgentBundle(agentXml, dependencies) {
// Inject web activation instructions into agent XML
agentXml = this.injectWebActivation(agentXml);
// Web activation is now handled by fragments during YAML building
// agentXml = this.injectWebActivation(agentXml);
const parts = [
'<?xml version="1.0" encoding="UTF-8"?>',
@@ -1222,17 +1275,13 @@ class WebBundler {
' ' + agentXml.replaceAll('\n', '\n '),
];
// Add dependencies without wrapper tags
// Add dependencies (all are now consistently wrapped in <file> elements)
if (dependencies && dependencies.size > 0) {
parts.push('\n <!-- Dependencies -->');
for (const [id, content] of dependencies) {
// Check if content is already wrapped in a <file> tag (from workflow processing)
// If so, don't escape it - it's already in CDATA
const isWrappedFile = content.trim().startsWith('<file ') && content.trim().endsWith('</file>');
const escapedContent = isWrappedFile ? content : this.escapeXmlContent(content);
// All dependencies are now wrapped in <file> elements
// Indent properly
const indentedContent = escapedContent
const indentedContent = content
.split('\n')
.map((line) => ' ' + line)
.join('\n');

View File

@@ -140,9 +140,9 @@ class WorkflowCommandGenerator {
## Execution
When running any workflow:
1. LOAD {project-root}/bmad/core/tasks/workflow.md
1. LOAD {project-root}/bmad/core/tasks/workflow.xml
2. Pass the workflow path as 'workflow-config' parameter
3. Follow workflow.md instructions EXACTLY
3. Follow workflow.xml instructions EXACTLY
4. Save outputs after EACH section
## Modes

View File

@@ -3,9 +3,9 @@
IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded:
<steps CRITICAL="TRUE">
1. Always LOAD the FULL {project-root}/bmad/core/tasks/workflow.md
1. Always LOAD the FULL {project-root}/bmad/core/tasks/workflow.xml
2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config {{workflow_path}}
3. Pass the yaml path {{workflow_path}} as 'workflow-config' parameter to the workflow.md instructions
4. Follow workflow.md instructions EXACTLY as written
3. Pass the yaml path {{workflow_path}} as 'workflow-config' parameter to the workflow.xml instructions
4. Follow workflow.xml instructions EXACTLY as written
5. Save outputs after EACH section when generating any documents from templates
</steps>

View File

@@ -38,13 +38,14 @@ class ActivationBuilder {
* @param {Object} profile - Agent profile from AgentAnalyzer
* @param {Object} metadata - Agent metadata (module, name, etc.)
* @param {Array} agentSpecificActions - Optional agent-specific critical actions
* @param {boolean} forWebBundle - Whether this is for a web bundle
* @returns {string} Complete activation block XML
*/
async buildActivation(profile, metadata = {}, agentSpecificActions = []) {
async buildActivation(profile, metadata = {}, agentSpecificActions = [], forWebBundle = false) {
let activation = '<activation critical="MANDATORY">\n';
// 1. Build sequential steps
const steps = await this.buildSteps(metadata, agentSpecificActions);
// 1. Build sequential steps (use web-specific steps for web bundles)
const steps = await this.buildSteps(metadata, agentSpecificActions, forWebBundle);
activation += this.indent(steps, 2) + '\n';
// 2. Build menu handlers section with dynamic handlers
@@ -60,9 +61,11 @@ class ActivationBuilder {
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';
// 3. Include rules (skip for web bundles as they're in web-bundle-activation-steps.xml)
if (!forWebBundle) {
const rules = await this.loadFragment('activation-rules.xml');
activation += this.indent(rules, 2) + '\n';
}
activation += '</activation>';
@@ -94,10 +97,13 @@ class ActivationBuilder {
* Build sequential activation steps
* @param {Object} metadata - Agent metadata
* @param {Array} agentSpecificActions - Optional agent-specific actions
* @param {boolean} forWebBundle - Whether this is for a web bundle
* @returns {string} Steps XML
*/
async buildSteps(metadata = {}, agentSpecificActions = []) {
const stepsTemplate = await this.loadFragment('activation-steps.xml');
async buildSteps(metadata = {}, agentSpecificActions = [], forWebBundle = false) {
// Use web-specific fragment for web bundles, standard fragment otherwise
const fragmentName = forWebBundle ? 'web-bundle-activation-steps.xml' : 'activation-steps.xml';
const stepsTemplate = await this.loadFragment(fragmentName);
// 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';

View File

@@ -141,7 +141,12 @@ class YamlXmlBuilder {
// Build activation block only if not skipped
let activationBlock = '';
if (!buildMetadata.skipActivation) {
activationBlock = await this.activationBuilder.buildActivation(profile, metadata, agent.critical_actions || []);
activationBlock = await this.activationBuilder.buildActivation(
profile,
metadata,
agent.critical_actions || [],
buildMetadata.forWebBundle || false, // Pass web bundle flag
);
}
// Start building XML
@@ -350,6 +355,7 @@ class YamlXmlBuilder {
builderVersion: '1.0.0',
includeMetadata: options.includeMetadata !== false,
skipActivation: options.skipActivation === true,
forWebBundle: options.forWebBundle === true,
};
// Convert to XML and return