claude-mpm 3.4.27__py3-none-any.whl → 3.5.1__py3-none-any.whl
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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/INSTRUCTIONS.md +182 -299
- claude_mpm/agents/agent_loader.py +283 -57
- claude_mpm/agents/agent_loader_integration.py +6 -9
- claude_mpm/agents/base_agent.json +2 -1
- claude_mpm/agents/base_agent_loader.py +1 -1
- claude_mpm/cli/__init__.py +5 -7
- claude_mpm/cli/commands/__init__.py +0 -2
- claude_mpm/cli/commands/agents.py +1 -1
- claude_mpm/cli/commands/memory.py +1 -1
- claude_mpm/cli/commands/run.py +12 -0
- claude_mpm/cli/parser.py +0 -13
- claude_mpm/cli/utils.py +1 -1
- claude_mpm/config/__init__.py +44 -2
- claude_mpm/config/agent_config.py +348 -0
- claude_mpm/config/paths.py +322 -0
- claude_mpm/constants.py +0 -1
- claude_mpm/core/__init__.py +2 -5
- claude_mpm/core/agent_registry.py +63 -17
- claude_mpm/core/claude_runner.py +354 -43
- claude_mpm/core/config.py +7 -1
- claude_mpm/core/config_aliases.py +4 -3
- claude_mpm/core/config_paths.py +151 -0
- claude_mpm/core/factories.py +4 -50
- claude_mpm/core/logger.py +11 -13
- claude_mpm/core/service_registry.py +2 -2
- claude_mpm/dashboard/static/js/components/agent-inference.js +101 -25
- claude_mpm/dashboard/static/js/components/event-processor.js +3 -2
- claude_mpm/hooks/claude_hooks/hook_handler.py +343 -83
- claude_mpm/hooks/memory_integration_hook.py +1 -1
- claude_mpm/init.py +37 -6
- claude_mpm/scripts/socketio_daemon.py +6 -2
- claude_mpm/services/__init__.py +71 -3
- claude_mpm/services/agents/__init__.py +85 -0
- claude_mpm/services/agents/deployment/__init__.py +21 -0
- claude_mpm/services/{agent_deployment.py → agents/deployment/agent_deployment.py} +192 -41
- claude_mpm/services/{agent_lifecycle_manager.py → agents/deployment/agent_lifecycle_manager.py} +11 -10
- claude_mpm/services/agents/loading/__init__.py +11 -0
- claude_mpm/services/{agent_profile_loader.py → agents/loading/agent_profile_loader.py} +9 -8
- claude_mpm/services/{base_agent_manager.py → agents/loading/base_agent_manager.py} +2 -2
- claude_mpm/services/{framework_agent_loader.py → agents/loading/framework_agent_loader.py} +116 -40
- claude_mpm/services/agents/management/__init__.py +9 -0
- claude_mpm/services/{agent_management_service.py → agents/management/agent_management_service.py} +6 -5
- claude_mpm/services/agents/memory/__init__.py +21 -0
- claude_mpm/services/{agent_memory_manager.py → agents/memory/agent_memory_manager.py} +3 -3
- claude_mpm/services/agents/registry/__init__.py +29 -0
- claude_mpm/services/{agent_registry.py → agents/registry/agent_registry.py} +101 -16
- claude_mpm/services/{deployed_agent_discovery.py → agents/registry/deployed_agent_discovery.py} +12 -2
- claude_mpm/services/{agent_modification_tracker.py → agents/registry/modification_tracker.py} +6 -5
- claude_mpm/services/async_session_logger.py +584 -0
- claude_mpm/services/claude_session_logger.py +299 -0
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +2 -2
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +17 -17
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +3 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +1 -1
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +1 -1
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +19 -24
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +1 -1
- claude_mpm/services/framework_claude_md_generator.py +4 -2
- claude_mpm/services/memory/__init__.py +17 -0
- claude_mpm/services/{memory_builder.py → memory/builder.py} +3 -3
- claude_mpm/services/memory/cache/__init__.py +14 -0
- claude_mpm/services/{shared_prompt_cache.py → memory/cache/shared_prompt_cache.py} +1 -1
- claude_mpm/services/memory/cache/simple_cache.py +317 -0
- claude_mpm/services/{memory_optimizer.py → memory/optimizer.py} +1 -1
- claude_mpm/services/{memory_router.py → memory/router.py} +1 -1
- claude_mpm/services/optimized_hook_service.py +542 -0
- claude_mpm/services/project_registry.py +14 -8
- claude_mpm/services/response_tracker.py +237 -0
- claude_mpm/services/ticketing_service_original.py +4 -2
- claude_mpm/services/version_control/branch_strategy.py +3 -1
- claude_mpm/utils/paths.py +12 -10
- claude_mpm/utils/session_logging.py +114 -0
- claude_mpm/validation/agent_validator.py +2 -1
- {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.1.dist-info}/METADATA +28 -20
- {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.1.dist-info}/RECORD +83 -106
- claude_mpm/cli/commands/ui.py +0 -57
- claude_mpm/core/simple_runner.py +0 -1046
- claude_mpm/hooks/builtin/__init__.py +0 -1
- claude_mpm/hooks/builtin/logging_hook_example.py +0 -165
- claude_mpm/hooks/builtin/memory_hooks_example.py +0 -67
- claude_mpm/hooks/builtin/mpm_command_hook.py +0 -125
- claude_mpm/hooks/builtin/post_delegation_hook_example.py +0 -124
- claude_mpm/hooks/builtin/pre_delegation_hook_example.py +0 -125
- claude_mpm/hooks/builtin/submit_hook_example.py +0 -100
- claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +0 -237
- claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +0 -240
- claude_mpm/hooks/builtin/workflow_start_hook.py +0 -181
- claude_mpm/orchestration/__init__.py +0 -6
- claude_mpm/orchestration/archive/direct_orchestrator.py +0 -195
- claude_mpm/orchestration/archive/factory.py +0 -215
- claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +0 -188
- claude_mpm/orchestration/archive/hook_integration_example.py +0 -178
- claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +0 -826
- claude_mpm/orchestration/archive/orchestrator.py +0 -501
- claude_mpm/orchestration/archive/pexpect_orchestrator.py +0 -252
- claude_mpm/orchestration/archive/pty_orchestrator.py +0 -270
- claude_mpm/orchestration/archive/simple_orchestrator.py +0 -82
- claude_mpm/orchestration/archive/subprocess_orchestrator.py +0 -801
- claude_mpm/orchestration/archive/system_prompt_orchestrator.py +0 -278
- claude_mpm/orchestration/archive/wrapper_orchestrator.py +0 -187
- claude_mpm/schemas/workflow_validator.py +0 -411
- claude_mpm/services/parent_directory_manager/__init__.py +0 -577
- claude_mpm/services/parent_directory_manager/backup_manager.py +0 -258
- claude_mpm/services/parent_directory_manager/config_manager.py +0 -210
- claude_mpm/services/parent_directory_manager/deduplication_manager.py +0 -279
- claude_mpm/services/parent_directory_manager/framework_protector.py +0 -143
- claude_mpm/services/parent_directory_manager/operations.py +0 -186
- claude_mpm/services/parent_directory_manager/state_manager.py +0 -624
- claude_mpm/services/parent_directory_manager/template_deployer.py +0 -579
- claude_mpm/services/parent_directory_manager/validation_manager.py +0 -378
- claude_mpm/services/parent_directory_manager/version_control_helper.py +0 -339
- claude_mpm/services/parent_directory_manager/version_manager.py +0 -222
- claude_mpm/ui/__init__.py +0 -1
- claude_mpm/ui/rich_terminal_ui.py +0 -295
- claude_mpm/ui/terminal_ui.py +0 -328
- /claude_mpm/services/{agent_versioning.py → agents/deployment/agent_versioning.py} +0 -0
- /claude_mpm/services/{agent_capabilities_generator.py → agents/management/agent_capabilities_generator.py} +0 -0
- /claude_mpm/services/{agent_persistence_service.py → agents/memory/agent_persistence_service.py} +0 -0
- {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.1.dist-info}/WHEEL +0 -0
- {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.1.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.1.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.1.dist-info}/top_level.txt +0 -0
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
"""Hook to log workflow information at the start of execution."""
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
from typing import Dict, Any, List, Optional
|
|
5
|
-
from datetime import datetime
|
|
6
|
-
|
|
7
|
-
from claude_mpm.hooks.base_hook import SubmitHook, HookContext, HookResult, HookType
|
|
8
|
-
from claude_mpm.core.logger import get_logger
|
|
9
|
-
|
|
10
|
-
logger = get_logger(__name__)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class WorkflowStartHook(SubmitHook):
|
|
14
|
-
"""Hook that logs workflow information including steps list at workflow start."""
|
|
15
|
-
|
|
16
|
-
def __init__(self):
|
|
17
|
-
super().__init__(name="workflow_start_logger", priority=5)
|
|
18
|
-
|
|
19
|
-
def execute(self, context: HookContext) -> HookResult:
|
|
20
|
-
"""Log workflow information when a workflow starts."""
|
|
21
|
-
try:
|
|
22
|
-
# Extract prompt and workflow data from context
|
|
23
|
-
prompt = context.data.get('prompt', '')
|
|
24
|
-
workflow_data = context.data.get('workflow', {})
|
|
25
|
-
|
|
26
|
-
# Check if this is a workflow start (either by explicit workflow data or detected pattern)
|
|
27
|
-
if workflow_data or self._is_workflow_prompt(prompt):
|
|
28
|
-
# Extract workflow information
|
|
29
|
-
workflow_name = workflow_data.get('name', 'Unnamed Workflow')
|
|
30
|
-
workflow_steps = workflow_data.get('steps', [])
|
|
31
|
-
|
|
32
|
-
# If no explicit steps provided, try to parse from prompt
|
|
33
|
-
if not workflow_steps and prompt:
|
|
34
|
-
workflow_steps = self._extract_steps_from_prompt(prompt)
|
|
35
|
-
|
|
36
|
-
# Log workflow start information
|
|
37
|
-
logger.info("="*60)
|
|
38
|
-
logger.info("WORKFLOW START")
|
|
39
|
-
logger.info("="*60)
|
|
40
|
-
logger.info(f"Workflow: {workflow_name}")
|
|
41
|
-
logger.info(f"Started at: {context.timestamp.isoformat()}")
|
|
42
|
-
logger.info(f"Session ID: {context.session_id or 'N/A'}")
|
|
43
|
-
logger.info(f"User ID: {context.user_id or 'N/A'}")
|
|
44
|
-
|
|
45
|
-
if workflow_steps:
|
|
46
|
-
logger.info(f"\nWorkflow Steps ({len(workflow_steps)} total):")
|
|
47
|
-
for i, step in enumerate(workflow_steps, 1):
|
|
48
|
-
if isinstance(step, dict):
|
|
49
|
-
step_name = step.get('name', step.get('description', 'Unnamed step'))
|
|
50
|
-
step_type = step.get('type', 'task')
|
|
51
|
-
logger.info(f" {i}. [{step_type}] {step_name}")
|
|
52
|
-
else:
|
|
53
|
-
# Handle simple string steps
|
|
54
|
-
logger.info(f" {i}. {step}")
|
|
55
|
-
else:
|
|
56
|
-
logger.info("\nNo explicit workflow steps defined")
|
|
57
|
-
|
|
58
|
-
# Log additional metadata if present
|
|
59
|
-
metadata = workflow_data.get('metadata', {})
|
|
60
|
-
if metadata:
|
|
61
|
-
logger.info(f"\nWorkflow Metadata:")
|
|
62
|
-
for key, value in metadata.items():
|
|
63
|
-
logger.info(f" {key}: {value}")
|
|
64
|
-
|
|
65
|
-
logger.info("="*60)
|
|
66
|
-
|
|
67
|
-
# Add workflow info to result metadata for downstream hooks
|
|
68
|
-
return HookResult(
|
|
69
|
-
success=True,
|
|
70
|
-
modified=False,
|
|
71
|
-
metadata={
|
|
72
|
-
'workflow_logged': True,
|
|
73
|
-
'workflow_name': workflow_name,
|
|
74
|
-
'step_count': len(workflow_steps),
|
|
75
|
-
'has_explicit_workflow': bool(workflow_data)
|
|
76
|
-
}
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
# Not a workflow start, pass through
|
|
80
|
-
return HookResult(success=True, modified=False)
|
|
81
|
-
|
|
82
|
-
except Exception as e:
|
|
83
|
-
logger.error(f"Workflow start logging failed: {e}")
|
|
84
|
-
# Don't block execution on logging errors
|
|
85
|
-
return HookResult(
|
|
86
|
-
success=True,
|
|
87
|
-
modified=False,
|
|
88
|
-
error=str(e)
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
def _is_workflow_prompt(self, prompt: str) -> bool:
|
|
92
|
-
"""Detect if a prompt indicates a workflow start."""
|
|
93
|
-
if not prompt:
|
|
94
|
-
return False
|
|
95
|
-
|
|
96
|
-
prompt_lower = prompt.lower()
|
|
97
|
-
workflow_indicators = [
|
|
98
|
-
'workflow', 'steps:', 'step 1', 'first,', 'then,', 'finally,',
|
|
99
|
-
'process:', 'procedure:', 'sequence:', 'plan:'
|
|
100
|
-
]
|
|
101
|
-
|
|
102
|
-
return any(indicator in prompt_lower for indicator in workflow_indicators)
|
|
103
|
-
|
|
104
|
-
def _extract_steps_from_prompt(self, prompt: str) -> List[str]:
|
|
105
|
-
"""Try to extract workflow steps from a prompt text."""
|
|
106
|
-
steps = []
|
|
107
|
-
|
|
108
|
-
# Look for numbered steps (1. 2. 3. or 1) 2) 3))
|
|
109
|
-
import re
|
|
110
|
-
numbered_pattern = re.compile(r'^\s*\d+[\)\.]\s*(.+)$', re.MULTILINE)
|
|
111
|
-
matches = numbered_pattern.findall(prompt)
|
|
112
|
-
if matches:
|
|
113
|
-
steps.extend(matches)
|
|
114
|
-
return steps
|
|
115
|
-
|
|
116
|
-
# Look for bullet points
|
|
117
|
-
bullet_pattern = re.compile(r'^\s*[-*•]\s*(.+)$', re.MULTILINE)
|
|
118
|
-
matches = bullet_pattern.findall(prompt)
|
|
119
|
-
if matches:
|
|
120
|
-
steps.extend(matches)
|
|
121
|
-
return steps
|
|
122
|
-
|
|
123
|
-
# Look for sequential keywords
|
|
124
|
-
sequential_pattern = re.compile(
|
|
125
|
-
r'(?:first|then|next|after that|finally|lastly),?\s*(.+?)(?=(?:first|then|next|after that|finally|lastly|$))',
|
|
126
|
-
re.IGNORECASE | re.DOTALL
|
|
127
|
-
)
|
|
128
|
-
matches = sequential_pattern.findall(prompt)
|
|
129
|
-
if matches:
|
|
130
|
-
steps.extend([m.strip() for m in matches if m.strip()])
|
|
131
|
-
|
|
132
|
-
return steps
|
|
133
|
-
|
|
134
|
-
def validate(self, context: HookContext) -> bool:
|
|
135
|
-
"""Validate if hook should run for given context."""
|
|
136
|
-
if not super().validate(context):
|
|
137
|
-
return False
|
|
138
|
-
|
|
139
|
-
# This hook runs for all submit contexts to check for workflows
|
|
140
|
-
return context.hook_type == HookType.SUBMIT
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
class WorkflowStepLogger(SubmitHook):
|
|
144
|
-
"""Alternative hook that logs individual workflow step execution."""
|
|
145
|
-
|
|
146
|
-
def __init__(self):
|
|
147
|
-
super().__init__(name="workflow_step_logger", priority=6)
|
|
148
|
-
|
|
149
|
-
def execute(self, context: HookContext) -> HookResult:
|
|
150
|
-
"""Log individual workflow step execution."""
|
|
151
|
-
try:
|
|
152
|
-
# Check if this is a workflow step execution
|
|
153
|
-
step_data = context.data.get('workflow_step', {})
|
|
154
|
-
if not step_data:
|
|
155
|
-
return HookResult(success=True, modified=False)
|
|
156
|
-
|
|
157
|
-
# Extract step information
|
|
158
|
-
step_number = step_data.get('number', 0)
|
|
159
|
-
step_name = step_data.get('name', 'Unnamed step')
|
|
160
|
-
step_type = step_data.get('type', 'task')
|
|
161
|
-
workflow_name = step_data.get('workflow_name', 'Unknown workflow')
|
|
162
|
-
|
|
163
|
-
# Log step execution
|
|
164
|
-
logger.info(f"\n→ Executing Step {step_number}: {step_name}")
|
|
165
|
-
logger.info(f" Type: {step_type}")
|
|
166
|
-
logger.info(f" Workflow: {workflow_name}")
|
|
167
|
-
logger.info(f" Started: {context.timestamp.isoformat()}")
|
|
168
|
-
|
|
169
|
-
return HookResult(
|
|
170
|
-
success=True,
|
|
171
|
-
modified=False,
|
|
172
|
-
metadata={
|
|
173
|
-
'step_logged': True,
|
|
174
|
-
'step_number': step_number,
|
|
175
|
-
'step_name': step_name
|
|
176
|
-
}
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
except Exception as e:
|
|
180
|
-
logger.error(f"Workflow step logging failed: {e}")
|
|
181
|
-
return HookResult(success=True, modified=False, error=str(e))
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
"""Direct orchestrator that runs Claude with minimal intervention."""
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import Optional
|
|
6
|
-
from datetime import datetime
|
|
7
|
-
import logging
|
|
8
|
-
import tempfile
|
|
9
|
-
|
|
10
|
-
try:
|
|
11
|
-
from ..core.logger import get_logger, setup_logging
|
|
12
|
-
from ..utils.subprocess_runner import SubprocessRunner
|
|
13
|
-
# TicketExtractor removed from project
|
|
14
|
-
from ..core.framework_loader import FrameworkLoader
|
|
15
|
-
from .agent_delegator import AgentDelegator
|
|
16
|
-
except ImportError:
|
|
17
|
-
from core.logger import get_logger, setup_logging
|
|
18
|
-
from utils.subprocess_runner import SubprocessRunner
|
|
19
|
-
# TicketExtractor removed from project
|
|
20
|
-
from core.framework_loader import FrameworkLoader
|
|
21
|
-
from orchestration.agent_delegator import AgentDelegator
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class DirectOrchestrator:
|
|
25
|
-
"""Orchestrator that runs Claude directly with framework injection via file."""
|
|
26
|
-
|
|
27
|
-
def __init__(
|
|
28
|
-
self,
|
|
29
|
-
framework_path: Optional[Path] = None,
|
|
30
|
-
agents_dir: Optional[Path] = None,
|
|
31
|
-
log_level: str = "OFF",
|
|
32
|
-
log_dir: Optional[Path] = None,
|
|
33
|
-
):
|
|
34
|
-
"""Initialize the orchestrator."""
|
|
35
|
-
self.log_level = log_level
|
|
36
|
-
self.log_dir = log_dir or (Path.home() / ".claude-mpm" / "logs")
|
|
37
|
-
|
|
38
|
-
# Set up logging
|
|
39
|
-
if log_level != "OFF":
|
|
40
|
-
self.logger = setup_logging(level=log_level, log_dir=log_dir)
|
|
41
|
-
self.logger.info(f"Initializing Direct Orchestrator (log_level={log_level})")
|
|
42
|
-
else:
|
|
43
|
-
# Minimal logger
|
|
44
|
-
self.logger = get_logger("direct_orchestrator")
|
|
45
|
-
self.logger.setLevel(logging.WARNING)
|
|
46
|
-
|
|
47
|
-
# Components
|
|
48
|
-
self.framework_loader = FrameworkLoader(framework_path, agents_dir)
|
|
49
|
-
# TicketExtractor removed from project
|
|
50
|
-
self.agent_delegator = AgentDelegator(self.framework_loader.agent_registry)
|
|
51
|
-
|
|
52
|
-
# State
|
|
53
|
-
self.session_start = datetime.now()
|
|
54
|
-
# Ticket creation removed from project
|
|
55
|
-
|
|
56
|
-
# Initialize subprocess runner
|
|
57
|
-
self.subprocess_runner = SubprocessRunner(logger=self.logger)
|
|
58
|
-
|
|
59
|
-
def run_interactive(self):
|
|
60
|
-
"""Run an interactive session by launching Claude directly."""
|
|
61
|
-
print("Claude MPM Interactive Session")
|
|
62
|
-
print("Framework will be injected on first interaction")
|
|
63
|
-
print("-" * 50)
|
|
64
|
-
|
|
65
|
-
# Get framework instructions
|
|
66
|
-
framework = self.framework_loader.get_framework_instructions()
|
|
67
|
-
|
|
68
|
-
# Save framework to a temporary file
|
|
69
|
-
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
|
|
70
|
-
f.write(framework)
|
|
71
|
-
f.write("\n\nNOTE: This is the claude-mpm framework. Please acknowledge you've received these instructions and then we can begin our session.\n")
|
|
72
|
-
framework_file = f.name
|
|
73
|
-
|
|
74
|
-
try:
|
|
75
|
-
# Log framework injection
|
|
76
|
-
if self.log_level != "OFF":
|
|
77
|
-
self.logger.info(f"Framework saved to temporary file: {framework_file}")
|
|
78
|
-
|
|
79
|
-
# Also save to prompts directory
|
|
80
|
-
prompt_path = Path.home() / ".claude-mpm" / "prompts"
|
|
81
|
-
prompt_path.mkdir(parents=True, exist_ok=True)
|
|
82
|
-
prompt_file = prompt_path / f"prompt_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
|
|
83
|
-
prompt_file.write_text(framework)
|
|
84
|
-
self.logger.info(f"Framework also saved to: {prompt_file}")
|
|
85
|
-
|
|
86
|
-
# Read the framework content
|
|
87
|
-
with open(framework_file, 'r') as f:
|
|
88
|
-
framework_content = f.read()
|
|
89
|
-
|
|
90
|
-
# Generate a unique session ID
|
|
91
|
-
import uuid
|
|
92
|
-
session_id = str(uuid.uuid4())
|
|
93
|
-
|
|
94
|
-
# Build command to start Claude with print mode and session ID
|
|
95
|
-
cmd = [
|
|
96
|
-
"claude",
|
|
97
|
-
"--model", "opus",
|
|
98
|
-
"--dangerously-skip-permissions",
|
|
99
|
-
"--session-id", session_id,
|
|
100
|
-
"--print", # Print mode
|
|
101
|
-
framework_content
|
|
102
|
-
]
|
|
103
|
-
|
|
104
|
-
self.logger.info(f"Starting Claude with framework injection (session: {session_id})")
|
|
105
|
-
|
|
106
|
-
# Run Claude with framework
|
|
107
|
-
print("\nInjecting framework instructions...")
|
|
108
|
-
result = self.subprocess_runner.run(cmd)
|
|
109
|
-
|
|
110
|
-
if result.success:
|
|
111
|
-
print("\nFramework injected. Claude's response:")
|
|
112
|
-
print("-" * 50)
|
|
113
|
-
print(result.stdout)
|
|
114
|
-
print("-" * 50)
|
|
115
|
-
|
|
116
|
-
# Debug: show stderr if logging is enabled
|
|
117
|
-
if self.log_level != "OFF" and result.stderr:
|
|
118
|
-
self.logger.debug(f"Claude stderr: {result.stderr}")
|
|
119
|
-
|
|
120
|
-
# Check if we can find the conversation file
|
|
121
|
-
# Parse stderr for conversation file location
|
|
122
|
-
conversation_file = None
|
|
123
|
-
if result.stderr:
|
|
124
|
-
import re
|
|
125
|
-
# Look for patterns like "Conversation saved to: /path/to/file"
|
|
126
|
-
match = re.search(r'(?:conversation saved to|saved to)[:\s]+([^\s]+)', result.stderr, re.I)
|
|
127
|
-
if match:
|
|
128
|
-
conversation_file = match.group(1).strip()
|
|
129
|
-
self.logger.info(f"Found conversation file: {conversation_file}")
|
|
130
|
-
|
|
131
|
-
# Now start interactive Claude session with same session ID
|
|
132
|
-
print("\nStarting interactive session...")
|
|
133
|
-
interactive_cmd = [
|
|
134
|
-
"claude",
|
|
135
|
-
"--model", "opus",
|
|
136
|
-
"--dangerously-skip-permissions"
|
|
137
|
-
]
|
|
138
|
-
|
|
139
|
-
# Try to continue the conversation
|
|
140
|
-
if conversation_file and Path(conversation_file).exists():
|
|
141
|
-
interactive_cmd.extend(["--continue", conversation_file])
|
|
142
|
-
print(f"Continuing conversation from: {conversation_file}")
|
|
143
|
-
elif session_id:
|
|
144
|
-
interactive_cmd.extend(["--session-id", session_id])
|
|
145
|
-
print(f"Using session ID: {session_id}")
|
|
146
|
-
|
|
147
|
-
# Run Claude interactively
|
|
148
|
-
self.subprocess_runner.run(interactive_cmd)
|
|
149
|
-
else:
|
|
150
|
-
print(f"Error injecting framework: {result.stderr}")
|
|
151
|
-
|
|
152
|
-
self.logger.info(f"Claude exited with code: {result.returncode}")
|
|
153
|
-
|
|
154
|
-
finally:
|
|
155
|
-
# Clean up temporary file
|
|
156
|
-
try:
|
|
157
|
-
os.unlink(framework_file)
|
|
158
|
-
except:
|
|
159
|
-
pass
|
|
160
|
-
|
|
161
|
-
# Ticket creation removed from project
|
|
162
|
-
|
|
163
|
-
# _create_tickets method removed - TicketExtractor functionality removed from project
|
|
164
|
-
|
|
165
|
-
def run_non_interactive(self, user_input: str):
|
|
166
|
-
"""Run a non-interactive session using print mode."""
|
|
167
|
-
try:
|
|
168
|
-
# Prepare message with framework
|
|
169
|
-
framework = self.framework_loader.get_framework_instructions()
|
|
170
|
-
full_message = framework + "\n\nUser: " + user_input
|
|
171
|
-
|
|
172
|
-
# Build command
|
|
173
|
-
cmd = [
|
|
174
|
-
"claude",
|
|
175
|
-
"--model", "opus",
|
|
176
|
-
"--dangerously-skip-permissions",
|
|
177
|
-
"--print", # Print mode
|
|
178
|
-
full_message
|
|
179
|
-
]
|
|
180
|
-
|
|
181
|
-
# Run Claude
|
|
182
|
-
result = self.subprocess_runner.run(cmd)
|
|
183
|
-
|
|
184
|
-
if result.success:
|
|
185
|
-
print(result.stdout)
|
|
186
|
-
|
|
187
|
-
# Ticket extraction removed from project
|
|
188
|
-
else:
|
|
189
|
-
print(f"Error: {result.stderr}")
|
|
190
|
-
|
|
191
|
-
# Ticket creation removed from project
|
|
192
|
-
|
|
193
|
-
except Exception as e:
|
|
194
|
-
print(f"Error: {e}")
|
|
195
|
-
self.logger.error(f"Non-interactive error: {e}")
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
"""Orchestrator factory for creating orchestrators based on mode and configuration."""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import Optional, Dict, Type, Any
|
|
6
|
-
from enum import Enum
|
|
7
|
-
|
|
8
|
-
from ..core.logger import get_logger
|
|
9
|
-
from .orchestrator import MPMOrchestrator
|
|
10
|
-
from .system_prompt_orchestrator import SystemPromptOrchestrator
|
|
11
|
-
from .subprocess_orchestrator import SubprocessOrchestrator
|
|
12
|
-
from .interactive_subprocess_orchestrator import InteractiveSubprocessOrchestrator
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class OrchestratorMode(Enum):
|
|
16
|
-
"""Available orchestrator modes."""
|
|
17
|
-
SYSTEM_PROMPT = "system_prompt"
|
|
18
|
-
SUBPROCESS = "subprocess"
|
|
19
|
-
INTERACTIVE_SUBPROCESS = "interactive_subprocess"
|
|
20
|
-
DIRECT = "direct"
|
|
21
|
-
PTY = "pty"
|
|
22
|
-
WRAPPER = "wrapper"
|
|
23
|
-
SIMPLE = "simple"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class OrchestratorFactory:
|
|
27
|
-
"""Factory for creating orchestrators based on mode and configuration.
|
|
28
|
-
|
|
29
|
-
This factory simplifies orchestrator selection and reduces complexity
|
|
30
|
-
in the run_session function by centralizing the logic for choosing
|
|
31
|
-
and instantiating the appropriate orchestrator.
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
# Registry of available orchestrators
|
|
35
|
-
_registry: Dict[OrchestratorMode, Type[MPMOrchestrator]] = {
|
|
36
|
-
OrchestratorMode.SYSTEM_PROMPT: SystemPromptOrchestrator,
|
|
37
|
-
OrchestratorMode.SUBPROCESS: SubprocessOrchestrator,
|
|
38
|
-
OrchestratorMode.INTERACTIVE_SUBPROCESS: InteractiveSubprocessOrchestrator,
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
def __init__(self):
|
|
42
|
-
"""Initialize the orchestrator factory."""
|
|
43
|
-
self.logger = get_logger(self.__class__.__name__)
|
|
44
|
-
self._discover_orchestrators()
|
|
45
|
-
|
|
46
|
-
def _discover_orchestrators(self):
|
|
47
|
-
"""Discover and register additional orchestrators.
|
|
48
|
-
|
|
49
|
-
This method enables automatic discovery of new orchestrator types
|
|
50
|
-
without modifying the factory code.
|
|
51
|
-
"""
|
|
52
|
-
# Try to import optional orchestrators
|
|
53
|
-
try:
|
|
54
|
-
from .direct_orchestrator import DirectOrchestrator
|
|
55
|
-
self._registry[OrchestratorMode.DIRECT] = DirectOrchestrator
|
|
56
|
-
self.logger.debug("Registered DirectOrchestrator")
|
|
57
|
-
except ImportError:
|
|
58
|
-
self.logger.debug("DirectOrchestrator not available")
|
|
59
|
-
|
|
60
|
-
try:
|
|
61
|
-
from .pty_orchestrator import PTYOrchestrator
|
|
62
|
-
self._registry[OrchestratorMode.PTY] = PTYOrchestrator
|
|
63
|
-
self.logger.debug("Registered PTYOrchestrator")
|
|
64
|
-
except ImportError:
|
|
65
|
-
self.logger.debug("PTYOrchestrator not available")
|
|
66
|
-
|
|
67
|
-
try:
|
|
68
|
-
from .wrapper_orchestrator import WrapperOrchestrator
|
|
69
|
-
self._registry[OrchestratorMode.WRAPPER] = WrapperOrchestrator
|
|
70
|
-
self.logger.debug("Registered WrapperOrchestrator")
|
|
71
|
-
except ImportError:
|
|
72
|
-
self.logger.debug("WrapperOrchestrator not available")
|
|
73
|
-
|
|
74
|
-
try:
|
|
75
|
-
from .simple_orchestrator import SimpleOrchestrator
|
|
76
|
-
self._registry[OrchestratorMode.SIMPLE] = SimpleOrchestrator
|
|
77
|
-
self.logger.debug("Registered SimpleOrchestrator")
|
|
78
|
-
except ImportError:
|
|
79
|
-
self.logger.debug("SimpleOrchestrator not available")
|
|
80
|
-
|
|
81
|
-
def create_orchestrator(
|
|
82
|
-
self,
|
|
83
|
-
mode: Optional[str] = None,
|
|
84
|
-
config: Optional[Dict[str, Any]] = None
|
|
85
|
-
) -> MPMOrchestrator:
|
|
86
|
-
"""Create an orchestrator based on mode and configuration.
|
|
87
|
-
|
|
88
|
-
Args:
|
|
89
|
-
mode: Orchestrator mode (string or OrchestratorMode enum)
|
|
90
|
-
config: Configuration dictionary containing:
|
|
91
|
-
- framework_path: Path to framework directory
|
|
92
|
-
- agents_dir: Custom agents directory
|
|
93
|
-
- log_level: Logging level (OFF, INFO, DEBUG)
|
|
94
|
-
- log_dir: Custom log directory
|
|
95
|
-
- hook_manager: Hook service manager instance
|
|
96
|
-
- enable_todo_hijacking: Enable TODO hijacking (subprocess mode)
|
|
97
|
-
- subprocess: Use subprocess orchestration
|
|
98
|
-
- interactive_subprocess: Use interactive subprocess
|
|
99
|
-
- Any other orchestrator-specific parameters
|
|
100
|
-
|
|
101
|
-
Returns:
|
|
102
|
-
Configured orchestrator instance
|
|
103
|
-
|
|
104
|
-
Raises:
|
|
105
|
-
ValueError: If mode is invalid or orchestrator creation fails
|
|
106
|
-
"""
|
|
107
|
-
if config is None:
|
|
108
|
-
config = {}
|
|
109
|
-
|
|
110
|
-
# Determine orchestrator mode
|
|
111
|
-
orchestrator_mode = self._determine_mode(mode, config)
|
|
112
|
-
|
|
113
|
-
# Validate mode
|
|
114
|
-
if orchestrator_mode not in self._registry:
|
|
115
|
-
available = ", ".join(m.value for m in self._registry.keys())
|
|
116
|
-
raise ValueError(
|
|
117
|
-
f"Invalid orchestrator mode: {orchestrator_mode.value}. "
|
|
118
|
-
f"Available modes: {available}"
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
# Get orchestrator class
|
|
122
|
-
orchestrator_class = self._registry[orchestrator_mode]
|
|
123
|
-
|
|
124
|
-
# Extract common parameters
|
|
125
|
-
common_params = {
|
|
126
|
-
"framework_path": config.get("framework_path"),
|
|
127
|
-
"agents_dir": config.get("agents_dir"),
|
|
128
|
-
"log_level": config.get("log_level", "OFF"),
|
|
129
|
-
"log_dir": config.get("log_dir"),
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
# Add hook manager if available
|
|
133
|
-
if "hook_manager" in config:
|
|
134
|
-
common_params["hook_manager"] = config["hook_manager"]
|
|
135
|
-
|
|
136
|
-
# Add mode-specific parameters
|
|
137
|
-
if orchestrator_mode == OrchestratorMode.SUBPROCESS:
|
|
138
|
-
common_params["enable_todo_hijacking"] = config.get("enable_todo_hijacking", False)
|
|
139
|
-
|
|
140
|
-
try:
|
|
141
|
-
# Create orchestrator instance
|
|
142
|
-
orchestrator = orchestrator_class(**common_params)
|
|
143
|
-
|
|
144
|
-
# Configure additional settings
|
|
145
|
-
if "no_tickets" in config and config["no_tickets"]:
|
|
146
|
-
orchestrator.ticket_creation_enabled = False
|
|
147
|
-
self.logger.info("Ticket creation disabled for orchestrator")
|
|
148
|
-
|
|
149
|
-
self.logger.info(
|
|
150
|
-
f"Created {orchestrator_class.__name__} "
|
|
151
|
-
f"(mode: {orchestrator_mode.value})"
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
return orchestrator
|
|
155
|
-
|
|
156
|
-
except Exception as e:
|
|
157
|
-
self.logger.error(f"Failed to create orchestrator: {e}")
|
|
158
|
-
raise ValueError(f"Failed to create orchestrator: {e}") from e
|
|
159
|
-
|
|
160
|
-
def _determine_mode(
|
|
161
|
-
self,
|
|
162
|
-
mode: Optional[str],
|
|
163
|
-
config: Dict[str, Any]
|
|
164
|
-
) -> OrchestratorMode:
|
|
165
|
-
"""Determine orchestrator mode from explicit mode or config flags.
|
|
166
|
-
|
|
167
|
-
Args:
|
|
168
|
-
mode: Explicit mode string
|
|
169
|
-
config: Configuration dictionary
|
|
170
|
-
|
|
171
|
-
Returns:
|
|
172
|
-
OrchestratorMode enum value
|
|
173
|
-
"""
|
|
174
|
-
# Always use subprocess orchestrator for simplicity
|
|
175
|
-
# This provides consistent behavior in both interactive and non-interactive modes
|
|
176
|
-
return OrchestratorMode.SUBPROCESS
|
|
177
|
-
|
|
178
|
-
def list_available_modes(self) -> Dict[str, Dict[str, Any]]:
|
|
179
|
-
"""List all available orchestrator modes with metadata.
|
|
180
|
-
|
|
181
|
-
Returns:
|
|
182
|
-
Dictionary mapping mode names to metadata
|
|
183
|
-
"""
|
|
184
|
-
modes = {}
|
|
185
|
-
for mode, orchestrator_class in self._registry.items():
|
|
186
|
-
modes[mode.value] = {
|
|
187
|
-
"class": orchestrator_class.__name__,
|
|
188
|
-
"module": orchestrator_class.__module__,
|
|
189
|
-
"description": orchestrator_class.__doc__.strip() if orchestrator_class.__doc__ else "No description",
|
|
190
|
-
}
|
|
191
|
-
return modes
|
|
192
|
-
|
|
193
|
-
def register_orchestrator(
|
|
194
|
-
self,
|
|
195
|
-
mode: OrchestratorMode,
|
|
196
|
-
orchestrator_class: Type[MPMOrchestrator]
|
|
197
|
-
):
|
|
198
|
-
"""Register a custom orchestrator.
|
|
199
|
-
|
|
200
|
-
Args:
|
|
201
|
-
mode: Orchestrator mode
|
|
202
|
-
orchestrator_class: Orchestrator class (must inherit from MPMOrchestrator)
|
|
203
|
-
|
|
204
|
-
Raises:
|
|
205
|
-
ValueError: If orchestrator class is invalid
|
|
206
|
-
"""
|
|
207
|
-
if not issubclass(orchestrator_class, MPMOrchestrator):
|
|
208
|
-
raise ValueError(
|
|
209
|
-
f"{orchestrator_class.__name__} must inherit from MPMOrchestrator"
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
self._registry[mode] = orchestrator_class
|
|
213
|
-
self.logger.info(
|
|
214
|
-
f"Registered {orchestrator_class.__name__} for mode {mode.value}"
|
|
215
|
-
)
|