Based on claude-code-tools TmuxCLIController, this refactor: - Added DockerTmuxController class for robust tmux session management - Implements send_keys() with configurable delay_enter - Implements capture_pane() for output retrieval - Implements wait_for_prompt() for pattern-based completion detection - Implements wait_for_idle() for content-hash-based idle detection - Implements wait_for_shell_prompt() for shell prompt detection Also includes workflow improvements: - Pre-task git snapshot before agent execution - Post-task commit protocol in agent guidelines Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
328 lines
11 KiB
Python
328 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Dispatcher-Plugin Integration - Seamless plugin skill integration into task dispatch
|
|
|
|
Bridges the responsive dispatcher with plugin skill matching to enable:
|
|
1. Automatic plugin skill detection for incoming tasks
|
|
2. Plugin metadata injection into dispatcher context
|
|
3. Skill-aware task routing
|
|
4. Plugin capability-based task optimization
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional, Any, Tuple
|
|
from datetime import datetime
|
|
|
|
from plugin_marketplace import PluginMarketplaceRegistry
|
|
from plugin_skill_loader import PluginSkillLoader
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class DispatcherPluginBridge:
|
|
"""
|
|
Integrates plugin skills into the responsive dispatcher workflow
|
|
|
|
Enhances task dispatch with:
|
|
- Automatic plugin skill detection
|
|
- Skill metadata injection into job context
|
|
- Plugin-aware task routing suggestions
|
|
"""
|
|
|
|
def __init__(self, registry: Optional[PluginMarketplaceRegistry] = None,
|
|
skill_loader: Optional[PluginSkillLoader] = None,
|
|
context_dir: Optional[Path] = None):
|
|
"""Initialize dispatcher-plugin bridge
|
|
|
|
Args:
|
|
registry: Plugin marketplace registry
|
|
skill_loader: Plugin skill loader
|
|
context_dir: Directory for storing enhanced task context
|
|
"""
|
|
self.registry = registry or PluginMarketplaceRegistry()
|
|
self.skill_loader = skill_loader or PluginSkillLoader(self.registry)
|
|
self.context_dir = context_dir or Path("/tmp/.luzia-plugin-context")
|
|
self.context_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Load all plugin skills on initialization
|
|
if not self.skill_loader.skills:
|
|
self.skill_loader.generate_skills_from_plugins()
|
|
|
|
def enhance_task_context(self, task_description: str,
|
|
project: str,
|
|
job_id: str) -> Dict[str, Any]:
|
|
"""
|
|
Enhance task context with relevant plugin skills
|
|
|
|
Args:
|
|
task_description: Description of the task
|
|
project: Project name
|
|
job_id: Job ID for tracking
|
|
|
|
Returns:
|
|
Enhanced context dict with plugin skill recommendations
|
|
"""
|
|
# Find relevant plugins and skills
|
|
matched_skills = self.skill_loader.find_skills_for_task(task_description, min_relevance=0.3)
|
|
matched_plugins = self.registry.find_plugins_for_task(
|
|
task_description,
|
|
self.skill_loader.matcher.extract_task_keywords(task_description)
|
|
)
|
|
|
|
# Extract context
|
|
context = {
|
|
'timestamp': datetime.now().isoformat(),
|
|
'job_id': job_id,
|
|
'project': project,
|
|
'task_description': task_description,
|
|
'plugin_analysis': {
|
|
'matched_plugins': [
|
|
{
|
|
'id': pid,
|
|
'name': self.registry.get_plugin(pid).name,
|
|
'relevance_score': score
|
|
}
|
|
for pid, score in matched_plugins[:3] # Top 3
|
|
],
|
|
'matched_skills': matched_skills[:5], # Top 5 skills
|
|
'total_skills_available': len(self.skill_loader.skills),
|
|
'analysis_timestamp': datetime.now().isoformat()
|
|
},
|
|
'recommended_plugins': self._generate_recommendations(matched_plugins, matched_skills),
|
|
'skill_metadata': self._compile_skill_metadata(matched_skills)
|
|
}
|
|
|
|
# Save context
|
|
context_file = self.context_dir / f"{job_id}_context.json"
|
|
context_file.write_text(json.dumps(context, indent=2))
|
|
|
|
return context
|
|
|
|
def _generate_recommendations(self, matched_plugins: List[Tuple[str, float]],
|
|
matched_skills: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
"""Generate actionable recommendations for task handling
|
|
|
|
Args:
|
|
matched_plugins: List of (plugin_id, score) tuples
|
|
matched_skills: List of matched skills
|
|
|
|
Returns:
|
|
Recommendations dict
|
|
"""
|
|
recommendations = {
|
|
'primary_skill': None,
|
|
'alternative_skills': [],
|
|
'required_capabilities': [],
|
|
'suggested_sequence': []
|
|
}
|
|
|
|
if matched_skills:
|
|
# Primary skill is the top-ranked one
|
|
recommendations['primary_skill'] = {
|
|
'skill_id': matched_skills[0]['skill_id'],
|
|
'name': matched_skills[0]['name'],
|
|
'plugin': matched_skills[0]['plugin_name'],
|
|
'confidence': matched_skills[0]['relevance_score']
|
|
}
|
|
|
|
# Alternative skills for fallback/additional analysis
|
|
if len(matched_skills) > 1:
|
|
recommendations['alternative_skills'] = [
|
|
{
|
|
'skill_id': skill['skill_id'],
|
|
'name': skill['name'],
|
|
'confidence': skill['relevance_score']
|
|
}
|
|
for skill in matched_skills[1:3]
|
|
]
|
|
|
|
# Extract unique capability categories
|
|
capability_categories = set()
|
|
for skill in matched_skills:
|
|
capability_categories.add(skill['category'])
|
|
|
|
recommendations['required_capabilities'] = list(capability_categories)
|
|
|
|
# Suggest execution sequence based on skill dependencies
|
|
recommendations['suggested_sequence'] = self._build_execution_sequence(matched_skills)
|
|
|
|
return recommendations
|
|
|
|
def _build_execution_sequence(self, matched_skills: List[Dict[str, Any]]) -> List[Dict[str, str]]:
|
|
"""Build suggested task execution sequence
|
|
|
|
Args:
|
|
matched_skills: List of matched skills
|
|
|
|
Returns:
|
|
List of execution steps
|
|
"""
|
|
sequence = []
|
|
|
|
# Group skills by category for logical ordering
|
|
categories_seen = set()
|
|
for skill in matched_skills[:5]: # Limit to top 5
|
|
category = skill['category']
|
|
if category not in categories_seen:
|
|
sequence.append({
|
|
'step': len(sequence) + 1,
|
|
'category': category,
|
|
'description': f"Execute {category} plugins",
|
|
'skills': [s['skill_id'] for s in matched_skills if s['category'] == category]
|
|
})
|
|
categories_seen.add(category)
|
|
|
|
return sequence
|
|
|
|
def _compile_skill_metadata(self, matched_skills: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
"""Compile comprehensive skill metadata
|
|
|
|
Args:
|
|
matched_skills: List of matched skills
|
|
|
|
Returns:
|
|
Compiled metadata
|
|
"""
|
|
metadata = {
|
|
'total_matched': len(matched_skills),
|
|
'by_category': {},
|
|
'by_trust_level': {},
|
|
'capabilities_available': []
|
|
}
|
|
|
|
for skill in matched_skills:
|
|
# Count by category
|
|
cat = skill['category']
|
|
metadata['by_category'][cat] = metadata['by_category'].get(cat, 0) + 1
|
|
|
|
# Count by trust level
|
|
trust = skill['trust_level']
|
|
metadata['by_trust_level'][trust] = metadata['by_trust_level'].get(trust, 0) + 1
|
|
|
|
# Collect unique capabilities
|
|
if skill['name'] not in metadata['capabilities_available']:
|
|
metadata['capabilities_available'].append(skill['name'])
|
|
|
|
return metadata
|
|
|
|
def get_task_context(self, job_id: str) -> Optional[Dict[str, Any]]:
|
|
"""Retrieve enhanced task context
|
|
|
|
Args:
|
|
job_id: Job ID
|
|
|
|
Returns:
|
|
Context dict or None if not found
|
|
"""
|
|
context_file = self.context_dir / f"{job_id}_context.json"
|
|
if context_file.exists():
|
|
try:
|
|
return json.loads(context_file.read_text())
|
|
except json.JSONDecodeError:
|
|
return None
|
|
return None
|
|
|
|
def export_dispatch_metadata(self) -> Dict[str, Any]:
|
|
"""Export metadata for dispatcher initialization
|
|
|
|
Returns:
|
|
Dict with all plugin dispatch metadata
|
|
"""
|
|
return {
|
|
'source': 'dispatcher-plugin-integration',
|
|
'timestamp': datetime.now().isoformat(),
|
|
'total_available_skills': len(self.skill_loader.skills),
|
|
'total_available_plugins': len(self.registry.plugins),
|
|
'skill_categories': list(self.skill_loader.category_index.keys()),
|
|
'skill_keywords': list(self.skill_loader.skill_index.keys()),
|
|
'dispatcher_enhancements': {
|
|
'enhanced_task_context': True,
|
|
'skill_detection': True,
|
|
'plugin_recommendations': True,
|
|
'execution_sequence_planning': True
|
|
}
|
|
}
|
|
|
|
|
|
class PluginAwareTaskDispatcher:
|
|
"""
|
|
Enhanced task dispatcher that leverages plugin skills
|
|
|
|
Wraps the responsive dispatcher with plugin-aware features for
|
|
intelligent task routing and context enrichment.
|
|
"""
|
|
|
|
def __init__(self, bridge: Optional[DispatcherPluginBridge] = None):
|
|
"""Initialize plugin-aware dispatcher
|
|
|
|
Args:
|
|
bridge: Dispatcher-plugin bridge instance
|
|
"""
|
|
self.bridge = bridge or DispatcherPluginBridge()
|
|
|
|
def dispatch_with_plugin_context(self, task_description: str,
|
|
project: str,
|
|
job_id: str,
|
|
priority: int = 5) -> Dict[str, Any]:
|
|
"""
|
|
Dispatch a task with automatic plugin skill detection and context enrichment
|
|
|
|
Args:
|
|
task_description: Description of the task
|
|
project: Project name
|
|
job_id: Job ID
|
|
priority: Task priority
|
|
|
|
Returns:
|
|
Enhanced dispatch result with plugin context
|
|
"""
|
|
# Enhance task context with plugin skills
|
|
enhanced_context = self.bridge.enhance_task_context(
|
|
task_description,
|
|
project,
|
|
job_id
|
|
)
|
|
|
|
# Build dispatch payload
|
|
dispatch_result = {
|
|
'job_id': job_id,
|
|
'project': project,
|
|
'task': task_description[:200],
|
|
'priority': priority,
|
|
'dispatched_at': datetime.now().isoformat(),
|
|
'plugin_enhanced': True,
|
|
'plugin_context': enhanced_context
|
|
}
|
|
|
|
logger.info(f"Dispatched job {job_id} with plugin context: "
|
|
f"{len(enhanced_context['plugin_analysis']['matched_skills'])} skills matched")
|
|
|
|
return dispatch_result
|
|
|
|
def get_dispatch_recommendations(self, job_id: str) -> Optional[Dict[str, Any]]:
|
|
"""Get plugin-based recommendations for a dispatched task
|
|
|
|
Args:
|
|
job_id: Job ID
|
|
|
|
Returns:
|
|
Recommendations or None
|
|
"""
|
|
context = self.bridge.get_task_context(job_id)
|
|
if context:
|
|
return context.get('recommended_plugins')
|
|
return None
|
|
|
|
|
|
# Convenience functions for integration with existing dispatcher
|
|
def get_dispatcher_bridge(registry: Optional[PluginMarketplaceRegistry] = None) -> DispatcherPluginBridge:
|
|
"""Get or create dispatcher-plugin bridge"""
|
|
return DispatcherPluginBridge(registry)
|
|
|
|
|
|
def get_plugin_aware_dispatcher() -> PluginAwareTaskDispatcher:
|
|
"""Get plugin-aware task dispatcher"""
|
|
return PluginAwareTaskDispatcher()
|