mega-minds
Version:
Enhanced multi-agent workflow system for Claude Code projects with automated handoff management and Claude Code hooks integration
321 lines (276 loc) • 11.3 kB
JavaScript
// lib/utils/TaskToolHandler.js
/**
* TaskToolHandler - Manages Task tool invocation formatting and validation
* This utility class helps format proper Task tool invocations for agent handoffs
* without breaking existing prompt-based workflows
*/
class TaskToolHandler {
constructor(config = {}) {
this.config = {
defaultSubagentType: 'general-purpose',
maxDescriptionLength: 50,
validateHandoffs: true,
...config
};
}
/**
* Check if Task tool integration is enabled
* @returns {boolean} True if Task tool should be used
*/
isTaskToolEnabled() {
return this.config.enableTaskTool === true;
}
/**
* Get configuration for TaskToolHandler
* @returns {object} Current configuration
*/
getConfig() {
return { ...this.config };
}
/**
* Update configuration
* @param {object} newConfig - Configuration updates
*/
updateConfig(newConfig) {
this.config = { ...this.config, ...newConfig };
}
/**
* Validate that required dependencies are available
* @returns {boolean} True if all dependencies are met
*/
validateDependencies() {
// Check if required modules are available
try {
// Basic validation - can be extended as needed
return typeof this.formatTaskInvocation === 'function';
} catch (error) {
console.warn('TaskToolHandler dependency validation failed:', error.message);
return false;
}
}
/**
* Format a Task tool invocation for agent handoff
* @param {string} targetAgent - The agent to hand off to (e.g., 'frontend-development-agent')
* @param {string} taskDescription - Brief description of the task (3-5 words)
* @param {string} handoffPrompt - Complete handoff prompt following communication protocol
* @param {object} options - Additional options for the Task tool
* @returns {object} Formatted Task tool invocation
*/
formatTaskInvocation(targetAgent, taskDescription, handoffPrompt, options = {}) {
// Validate inputs
if (!targetAgent || typeof targetAgent !== 'string') {
throw new Error('Target agent name is required and must be a string');
}
if (!taskDescription || typeof taskDescription !== 'string') {
throw new Error('Task description is required and must be a string');
}
if (!handoffPrompt || typeof handoffPrompt !== 'string') {
throw new Error('Handoff prompt is required and must be a string');
}
// Validate description length
if (taskDescription.length > this.config.maxDescriptionLength) {
throw new Error(`Task description must be ${this.config.maxDescriptionLength} characters or less`);
}
// Build the Task tool invocation
const taskInvocation = {
function_calls: [
{
invoke: {
name: 'Task',
parameters: {
subagent_type: options.subagentType || this.config.defaultSubagentType,
description: taskDescription,
prompt: handoffPrompt
}
}
}
]
};
return taskInvocation;
}
/**
* Format a Task tool invocation as XML string (Claude Code format)
* @param {string} targetAgent - The agent to hand off to
* @param {string} taskDescription - Brief description of the task
* @param {string} handoffPrompt - Complete handoff prompt
* @param {object} options - Additional options
* @returns {string} XML-formatted Task tool invocation
*/
formatTaskInvocationXML(targetAgent, taskDescription, handoffPrompt, options = {}) {
const subagentType = options.subagentType || this.config.defaultSubagentType;
// Escape XML special characters in the prompt
const escapedPrompt = this.escapeXML(handoffPrompt);
const escapedDescription = this.escapeXML(taskDescription);
return `<function_calls>
<invoke name="Task">
<parameter name="subagent_type">${subagentType}</parameter>
<parameter name="description">${escapedDescription}</parameter>
<parameter name="prompt">${escapedPrompt}</parameter>
</invoke>
</function_calls>`;
}
/**
* Escape XML special characters
* @param {string} text - Text to escape
* @returns {string} XML-escaped text
*/
escapeXML(text) {
if (typeof text !== 'string') return text;
return text
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
/**
* Validate handoff parameters meet communication protocol requirements
* @param {object} handoffData - The handoff data to validate
* @returns {object} Validation result with isValid flag and errors array
*/
validateHandoffParameters(handoffData) {
const errors = [];
const warnings = [];
// Required fields validation
const requiredFields = ['targetAgent', 'taskDescription', 'context', 'requirements', 'successCriteria'];
for (const field of requiredFields) {
if (!handoffData[field]) {
errors.push(`Missing required field: ${field}`);
}
}
// Agent name validation
if (handoffData.targetAgent) {
if (!handoffData.targetAgent.includes('-agent')) {
warnings.push('Target agent should follow naming convention: [name]-agent');
}
if (handoffData.targetAgent.length > 50) {
errors.push('Target agent name too long (max 50 characters)');
}
}
// Task description validation
if (handoffData.taskDescription) {
const wordCount = handoffData.taskDescription.trim().split(/\s+/).length;
if (wordCount > 10) {
warnings.push('Task description should be 3-5 words for optimal clarity');
}
if (handoffData.taskDescription.length > this.config.maxDescriptionLength) {
errors.push(`Task description too long (max ${this.config.maxDescriptionLength} characters)`);
}
}
// Context validation
if (handoffData.context && handoffData.context.length < 50) {
warnings.push('Context seems too brief - consider adding more background information');
}
// Success criteria validation
if (handoffData.successCriteria && Array.isArray(handoffData.successCriteria)) {
if (handoffData.successCriteria.length === 0) {
errors.push('Success criteria array is empty - at least one criteria required');
}
} else if (handoffData.successCriteria && typeof handoffData.successCriteria === 'string') {
if (handoffData.successCriteria.length < 20) {
warnings.push('Success criteria seems too brief - be more specific');
}
}
return {
isValid: errors.length === 0,
errors: errors,
warnings: warnings,
score: this.calculateHandoffQualityScore(handoffData, errors, warnings)
};
}
/**
* Calculate a quality score for the handoff (0-100)
* @param {object} handoffData - The handoff data
* @param {array} errors - Validation errors
* @param {array} warnings - Validation warnings
* @returns {number} Quality score from 0-100
*/
calculateHandoffQualityScore(handoffData, errors, warnings) {
let score = 100;
// Subtract points for errors (blocking issues)
score -= errors.length * 25;
// Subtract points for warnings (quality issues)
score -= warnings.length * 10;
// Bonus points for completeness
const optionalFields = ['timeline', 'dependencies', 'integrationPoints', 'nextAgent'];
const completedOptionalFields = optionalFields.filter(field => handoffData[field]).length;
score += completedOptionalFields * 5;
return Math.max(0, Math.min(100, score));
}
/**
* Validate that handoff prompt follows communication protocol template
* @param {string} handoffPrompt - The complete handoff prompt
* @returns {object} Validation result
*/
validateHandoffTemplate(handoffPrompt) {
const errors = [];
const warnings = [];
if (!handoffPrompt || typeof handoffPrompt !== 'string') {
errors.push('Handoff prompt is required and must be a string');
return { isValid: false, errors, warnings };
}
// Check for required template sections
const requiredSections = [
'## Handoff to @',
'🤖 @',
'**Context**:',
'**Your Task**:',
'**Success Criteria**:'
];
for (const section of requiredSections) {
if (!handoffPrompt.includes(section)) {
errors.push(`Missing required template section: ${section}`);
}
}
// Check for visual activation marker
if (!handoffPrompt.includes('🤖 @') || !handoffPrompt.includes('ACTIVE')) {
errors.push('Missing required visual activation marker: 🤖 @[agent-name] ACTIVE');
}
// Check template structure quality
if (!handoffPrompt.includes('**Requirements & Constraints**:')) {
warnings.push('Consider adding Requirements & Constraints section');
}
if (!handoffPrompt.includes('**Integration Points**:')) {
warnings.push('Consider adding Integration Points section');
}
if (!handoffPrompt.includes('**Timeline**:')) {
warnings.push('Consider adding Timeline section');
}
return {
isValid: errors.length === 0,
errors: errors,
warnings: warnings,
templateCompleteness: this.calculateTemplateCompleteness(handoffPrompt)
};
}
/**
* Calculate template completeness percentage
* @param {string} handoffPrompt - The handoff prompt
* @returns {number} Completeness percentage (0-100)
*/
calculateTemplateCompleteness(handoffPrompt) {
const allSections = [
'## Handoff to @',
'🤖 @',
'**Context**:',
'**Your Task**:',
'**Requirements & Constraints**:',
'**Success Criteria**:',
'**Dependencies**:',
'**Integration Points**:',
'**Timeline**:'
];
const presentSections = allSections.filter(section =>
handoffPrompt.includes(section)
).length;
return Math.round((presentSections / allSections.length) * 100);
}
/**
* Get the current version of TaskToolHandler
* @returns {string} Version string
*/
getVersion() {
return '1.0.0';
}
}
module.exports = TaskToolHandler;