claude-mpm 3.4.26__py3-none-any.whl → 3.5.0__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 +6 -10
- 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.26.dist-info → claude_mpm-3.5.0.dist-info}/METADATA +26 -20
- {claude_mpm-3.4.26.dist-info → claude_mpm-3.5.0.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.26.dist-info → claude_mpm-3.5.0.dist-info}/WHEEL +0 -0
- {claude_mpm-3.4.26.dist-info → claude_mpm-3.5.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.4.26.dist-info → claude_mpm-3.5.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.4.26.dist-info → claude_mpm-3.5.0.dist-info}/top_level.txt +0 -0
|
@@ -1,801 +0,0 @@
|
|
|
1
|
-
"""Subprocess orchestrator that mimics Claude's Task tool for non-interactive mode."""
|
|
2
|
-
|
|
3
|
-
import concurrent.futures
|
|
4
|
-
import json
|
|
5
|
-
import time
|
|
6
|
-
import logging
|
|
7
|
-
import subprocess
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
from typing import Dict, List, Any, Optional, Tuple
|
|
10
|
-
from datetime import datetime
|
|
11
|
-
import re
|
|
12
|
-
|
|
13
|
-
try:
|
|
14
|
-
from ..core.logger import get_logger, setup_logging
|
|
15
|
-
from ..utils.subprocess_runner import SubprocessRunner
|
|
16
|
-
# TicketExtractor removed from project
|
|
17
|
-
from ..core.framework_loader import FrameworkLoader
|
|
18
|
-
from ..core.claude_launcher import ClaudeLauncher, LaunchMode
|
|
19
|
-
from .agent_delegator import AgentDelegator
|
|
20
|
-
from ..hooks.hook_client import HookServiceClient
|
|
21
|
-
from .todo_hijacker import TodoHijacker
|
|
22
|
-
from ..core.logger import get_project_logger
|
|
23
|
-
from ..core.tool_access_control import tool_access_control
|
|
24
|
-
from ..core.agent_name_normalizer import agent_name_normalizer
|
|
25
|
-
except ImportError:
|
|
26
|
-
from core.logger import get_logger, setup_logging
|
|
27
|
-
from utils.subprocess_runner import SubprocessRunner
|
|
28
|
-
# TicketExtractor removed from project
|
|
29
|
-
from core.framework_loader import FrameworkLoader
|
|
30
|
-
from core.claude_launcher import ClaudeLauncher, LaunchMode
|
|
31
|
-
from orchestration.agent_delegator import AgentDelegator
|
|
32
|
-
from orchestration.todo_hijacker import TodoHijacker
|
|
33
|
-
from core.logger import get_project_logger
|
|
34
|
-
from core.tool_access_control import tool_access_control
|
|
35
|
-
from core.agent_name_normalizer import agent_name_normalizer
|
|
36
|
-
try:
|
|
37
|
-
from hooks.hook_client import HookServiceClient
|
|
38
|
-
except ImportError:
|
|
39
|
-
HookServiceClient = None
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class SubprocessOrchestrator:
|
|
43
|
-
"""
|
|
44
|
-
Orchestrator that creates real subprocesses for agent delegations.
|
|
45
|
-
Mimics Claude's built-in Task tool behavior for non-interactive mode.
|
|
46
|
-
"""
|
|
47
|
-
|
|
48
|
-
def __init__(
|
|
49
|
-
self,
|
|
50
|
-
framework_path: Optional[Path] = None,
|
|
51
|
-
agents_dir: Optional[Path] = None,
|
|
52
|
-
log_level: str = "OFF",
|
|
53
|
-
log_dir: Optional[Path] = None,
|
|
54
|
-
hook_manager=None,
|
|
55
|
-
enable_todo_hijacking: bool = False,
|
|
56
|
-
):
|
|
57
|
-
"""Initialize the subprocess orchestrator."""
|
|
58
|
-
self.log_level = log_level
|
|
59
|
-
self.log_dir = log_dir or (Path.home() / ".claude-mpm" / "logs")
|
|
60
|
-
self.hook_manager = hook_manager
|
|
61
|
-
|
|
62
|
-
# Initialize unified Claude launcher
|
|
63
|
-
self.launcher = ClaudeLauncher(model="opus", skip_permissions=True, log_level=log_level)
|
|
64
|
-
self.enable_todo_hijacking = enable_todo_hijacking
|
|
65
|
-
|
|
66
|
-
# Set up logging
|
|
67
|
-
if log_level != "OFF":
|
|
68
|
-
self.logger = setup_logging(level=log_level, log_dir=log_dir)
|
|
69
|
-
self.logger.info(f"Initializing Subprocess Orchestrator (log_level={log_level})")
|
|
70
|
-
if hook_manager and hook_manager.is_available():
|
|
71
|
-
self.logger.info(f"Hook service available on port {hook_manager.port}")
|
|
72
|
-
else:
|
|
73
|
-
self.logger = get_logger("subprocess_orchestrator")
|
|
74
|
-
self.logger.setLevel(logging.WARNING)
|
|
75
|
-
|
|
76
|
-
# Components
|
|
77
|
-
self.framework_loader = FrameworkLoader(framework_path, agents_dir)
|
|
78
|
-
# TicketExtractor removed from project
|
|
79
|
-
self.agent_delegator = AgentDelegator(self.framework_loader.agent_registry)
|
|
80
|
-
|
|
81
|
-
# Initialize TODO hijacker if enabled
|
|
82
|
-
self.todo_hijacker = None
|
|
83
|
-
if enable_todo_hijacking:
|
|
84
|
-
self.todo_hijacker = TodoHijacker(
|
|
85
|
-
log_level=log_level,
|
|
86
|
-
on_delegation=self._handle_todo_delegation
|
|
87
|
-
)
|
|
88
|
-
self.logger.info("TODO hijacking enabled")
|
|
89
|
-
|
|
90
|
-
# Initialize hook client if available
|
|
91
|
-
self.hook_client = None
|
|
92
|
-
if self.hook_manager and self.hook_manager.is_available() and HookServiceClient:
|
|
93
|
-
try:
|
|
94
|
-
hook_info = self.hook_manager.get_service_info()
|
|
95
|
-
if hook_info and 'url' in hook_info:
|
|
96
|
-
self.hook_client = HookServiceClient(base_url=hook_info['url'])
|
|
97
|
-
# Test connection
|
|
98
|
-
health = self.hook_client.health_check()
|
|
99
|
-
if health.get('status') == 'healthy':
|
|
100
|
-
self.logger.info(f"Connected to hook service with {health.get('hooks_count', 0)} hooks")
|
|
101
|
-
else:
|
|
102
|
-
self.logger.warning("Hook service not healthy, disabling hooks")
|
|
103
|
-
self.hook_client = None
|
|
104
|
-
else:
|
|
105
|
-
self.logger.debug("Hook service info missing 'url' key, skipping hook client initialization")
|
|
106
|
-
except Exception as e:
|
|
107
|
-
self.logger.warning(f"Failed to initialize hook client: {e}")
|
|
108
|
-
self.hook_client = None
|
|
109
|
-
|
|
110
|
-
# State
|
|
111
|
-
self.session_start = datetime.now()
|
|
112
|
-
# Ticket creation removed from project
|
|
113
|
-
self._pending_todo_delegations = []
|
|
114
|
-
|
|
115
|
-
# Initialize subprocess runner
|
|
116
|
-
self.subprocess_runner = SubprocessRunner(logger=self.logger)
|
|
117
|
-
|
|
118
|
-
# Initialize project logger
|
|
119
|
-
self.project_logger = get_project_logger(log_level)
|
|
120
|
-
self.project_logger.log_system(
|
|
121
|
-
f"Initializing Subprocess Orchestrator (log_level={log_level})",
|
|
122
|
-
level="INFO",
|
|
123
|
-
component="orchestrator"
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
def detect_delegations(self, response: str) -> List[Dict[str, str]]:
|
|
127
|
-
"""
|
|
128
|
-
Detect delegation requests in PM response.
|
|
129
|
-
|
|
130
|
-
Looks for patterns like:
|
|
131
|
-
- **Engineer Agent**: Create a function...
|
|
132
|
-
- Task(Engineer role summary)
|
|
133
|
-
- Delegate to Engineer: implement...
|
|
134
|
-
|
|
135
|
-
Returns:
|
|
136
|
-
List of delegations with agent and task
|
|
137
|
-
"""
|
|
138
|
-
delegations = []
|
|
139
|
-
|
|
140
|
-
# Pattern 1: **Agent Name**: task
|
|
141
|
-
pattern1 = r'\*\*([^*]+)(?:\s+Agent)?\*\*:\s*(.+?)(?=\n\n|\n\*\*|$)'
|
|
142
|
-
for match in re.finditer(pattern1, response, re.MULTILINE | re.DOTALL):
|
|
143
|
-
agent = agent_name_normalizer.normalize(match.group(1).strip())
|
|
144
|
-
task = match.group(2).strip()
|
|
145
|
-
delegations.append({
|
|
146
|
-
'agent': agent,
|
|
147
|
-
'task': task,
|
|
148
|
-
'format': 'markdown'
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
# Pattern 2: Task(description)
|
|
152
|
-
pattern2 = r'Task\(([^)]+)\)'
|
|
153
|
-
for match in re.finditer(pattern2, response):
|
|
154
|
-
task_desc = match.group(1).strip()
|
|
155
|
-
# Try to extract agent from task description
|
|
156
|
-
agent = self._extract_agent_from_task(task_desc)
|
|
157
|
-
delegations.append({
|
|
158
|
-
'agent': agent,
|
|
159
|
-
'task': task_desc,
|
|
160
|
-
'format': 'task_tool'
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
if self.log_level != "OFF":
|
|
164
|
-
self.logger.info(f"Detected {len(delegations)} delegations")
|
|
165
|
-
for d in delegations:
|
|
166
|
-
self.logger.debug(f" {d['agent']}: {d['task'][:50]}...")
|
|
167
|
-
|
|
168
|
-
return delegations
|
|
169
|
-
|
|
170
|
-
def _extract_agent_from_task(self, task: str) -> str:
|
|
171
|
-
"""Extract agent name from task description."""
|
|
172
|
-
# Try to extract from TODO format first
|
|
173
|
-
agent = agent_name_normalizer.extract_from_todo(task)
|
|
174
|
-
if agent:
|
|
175
|
-
return agent
|
|
176
|
-
|
|
177
|
-
# Otherwise, look for agent mentions in the task
|
|
178
|
-
task_lower = task.lower()
|
|
179
|
-
|
|
180
|
-
# Check for explicit agent names
|
|
181
|
-
agents = ['engineer', 'qa', 'documentation', 'research',
|
|
182
|
-
'security', 'ops', 'version control', 'data engineer']
|
|
183
|
-
|
|
184
|
-
for agent in agents:
|
|
185
|
-
if agent in task_lower:
|
|
186
|
-
return agent_name_normalizer.normalize(agent)
|
|
187
|
-
|
|
188
|
-
# Use agent delegator to suggest based on task
|
|
189
|
-
suggested = self.agent_delegator.suggest_agent_for_task(task)
|
|
190
|
-
return agent_name_normalizer.normalize(suggested) if suggested else "Engineer"
|
|
191
|
-
|
|
192
|
-
def create_agent_prompt(self, agent: str, task: str) -> str:
|
|
193
|
-
"""
|
|
194
|
-
Create a prompt for an agent subprocess.
|
|
195
|
-
|
|
196
|
-
Args:
|
|
197
|
-
agent: Agent name
|
|
198
|
-
task: Task description
|
|
199
|
-
|
|
200
|
-
Returns:
|
|
201
|
-
Complete prompt including agent-specific framework
|
|
202
|
-
"""
|
|
203
|
-
# Normalize agent name
|
|
204
|
-
normalized_agent = agent_name_normalizer.normalize(agent)
|
|
205
|
-
|
|
206
|
-
# Get agent-specific content
|
|
207
|
-
agent_content = ""
|
|
208
|
-
agent_key = agent_name_normalizer.to_key(normalized_agent) + '_agent'
|
|
209
|
-
|
|
210
|
-
if agent_key in self.framework_loader.framework_content.get('agents', {}):
|
|
211
|
-
agent_content = self.framework_loader.framework_content['agents'][agent_key]
|
|
212
|
-
|
|
213
|
-
# Get TODO guidance for this agent
|
|
214
|
-
todo_guidance = tool_access_control.get_todo_guidance(agent)
|
|
215
|
-
|
|
216
|
-
# Build focused agent prompt
|
|
217
|
-
prompt = f"""You are the {normalized_agent} Agent in the Claude PM Framework.
|
|
218
|
-
|
|
219
|
-
{agent_content}
|
|
220
|
-
|
|
221
|
-
## Tool Access and Task Tracking
|
|
222
|
-
{todo_guidance}
|
|
223
|
-
|
|
224
|
-
## Current Task
|
|
225
|
-
{task}
|
|
226
|
-
|
|
227
|
-
## Response Format
|
|
228
|
-
Provide a clear, structured response that:
|
|
229
|
-
1. Confirms your role as {normalized_agent} Agent
|
|
230
|
-
2. Completes the requested task
|
|
231
|
-
3. Reports any issues or blockers
|
|
232
|
-
4. Summarizes deliverables
|
|
233
|
-
5. Lists any follow-up tasks using TODO format
|
|
234
|
-
|
|
235
|
-
## TODO Reporting Format
|
|
236
|
-
When identifying tasks for other agents, use this format:
|
|
237
|
-
TODO (Priority): [Agent] Task description
|
|
238
|
-
|
|
239
|
-
Example:
|
|
240
|
-
TODO (High Priority): [Research] Analyze authentication patterns
|
|
241
|
-
TODO (Medium Priority): [QA] Write tests for new endpoints
|
|
242
|
-
|
|
243
|
-
Remember: You are an autonomous agent. Complete the task independently and report results."""
|
|
244
|
-
|
|
245
|
-
return prompt
|
|
246
|
-
|
|
247
|
-
def run_subprocess(self, agent: str, task: str) -> Tuple[str, float, int]:
|
|
248
|
-
"""
|
|
249
|
-
Run a single agent subprocess.
|
|
250
|
-
|
|
251
|
-
Args:
|
|
252
|
-
agent: Agent name
|
|
253
|
-
task: Task description
|
|
254
|
-
|
|
255
|
-
Returns:
|
|
256
|
-
Tuple of (response, execution_time, token_count)
|
|
257
|
-
"""
|
|
258
|
-
start_time = time.time()
|
|
259
|
-
|
|
260
|
-
# Pre-delegation hook
|
|
261
|
-
if self.hook_client:
|
|
262
|
-
try:
|
|
263
|
-
self.logger.info(f"Calling pre-delegation hook for {agent}")
|
|
264
|
-
hook_results = self.hook_client.execute_pre_delegation_hook(
|
|
265
|
-
agent=agent,
|
|
266
|
-
context={"task": task}
|
|
267
|
-
)
|
|
268
|
-
if hook_results:
|
|
269
|
-
self.logger.info(f"Pre-delegation hook executed: {len(hook_results)} hooks")
|
|
270
|
-
# Check for any modifications
|
|
271
|
-
modified = self.hook_client.get_modified_data(hook_results)
|
|
272
|
-
if modified and 'task' in modified:
|
|
273
|
-
task = modified['task']
|
|
274
|
-
self.logger.info(f"Task modified by hook: {task[:50]}...")
|
|
275
|
-
except Exception as e:
|
|
276
|
-
self.logger.warning(f"Pre-delegation hook error: {e}")
|
|
277
|
-
|
|
278
|
-
# Create agent prompt
|
|
279
|
-
prompt = self.create_agent_prompt(agent, task)
|
|
280
|
-
|
|
281
|
-
# Log prompt size
|
|
282
|
-
token_estimate = len(prompt) // 4 # Rough estimate
|
|
283
|
-
if self.log_level != "OFF":
|
|
284
|
-
self.logger.info(f"Running subprocess for {agent} ({token_estimate} est. tokens)")
|
|
285
|
-
|
|
286
|
-
# Log the agent invocation
|
|
287
|
-
self.project_logger.log_system(
|
|
288
|
-
f"Invoking {agent} agent for task: {task[:100]}",
|
|
289
|
-
level="INFO",
|
|
290
|
-
component="orchestrator"
|
|
291
|
-
)
|
|
292
|
-
|
|
293
|
-
try:
|
|
294
|
-
# Normalize agent name for consistent tool access
|
|
295
|
-
normalized_agent = agent_name_normalizer.normalize(agent)
|
|
296
|
-
|
|
297
|
-
# Get allowed tools for this agent type
|
|
298
|
-
allowed_tools = tool_access_control.format_allowed_tools_arg(normalized_agent, is_parent=False)
|
|
299
|
-
|
|
300
|
-
if self.log_level != "OFF":
|
|
301
|
-
self.logger.info(f"Applying tool restrictions for {normalized_agent}: {allowed_tools}")
|
|
302
|
-
|
|
303
|
-
# Create command with tool restrictions
|
|
304
|
-
cmd = [self.launcher.claude_path, "--dangerously-skip-permissions"]
|
|
305
|
-
if self.launcher.model:
|
|
306
|
-
cmd.extend(["--model", self.launcher.model])
|
|
307
|
-
|
|
308
|
-
# Apply tool restrictions for the agent
|
|
309
|
-
cmd.extend(["--allowedTools", allowed_tools])
|
|
310
|
-
|
|
311
|
-
# Use subprocess directly for more control
|
|
312
|
-
import subprocess
|
|
313
|
-
process = subprocess.Popen(
|
|
314
|
-
cmd,
|
|
315
|
-
stdin=subprocess.PIPE,
|
|
316
|
-
stdout=subprocess.PIPE,
|
|
317
|
-
stderr=subprocess.PIPE,
|
|
318
|
-
text=True
|
|
319
|
-
)
|
|
320
|
-
|
|
321
|
-
stdout, stderr = process.communicate(input=prompt, timeout=60)
|
|
322
|
-
returncode = process.returncode
|
|
323
|
-
|
|
324
|
-
execution_time = time.time() - start_time
|
|
325
|
-
|
|
326
|
-
if returncode == 0:
|
|
327
|
-
response = stdout.strip()
|
|
328
|
-
# Estimate response tokens
|
|
329
|
-
total_tokens = len(prompt + response) // 4
|
|
330
|
-
|
|
331
|
-
# Post-delegation hook
|
|
332
|
-
if self.hook_client:
|
|
333
|
-
try:
|
|
334
|
-
self.logger.info(f"Calling post-delegation hook for {agent}")
|
|
335
|
-
hook_results = self.hook_client.execute_post_delegation_hook(
|
|
336
|
-
agent=agent,
|
|
337
|
-
result={
|
|
338
|
-
"task": task,
|
|
339
|
-
"response": response,
|
|
340
|
-
"execution_time": execution_time,
|
|
341
|
-
"tokens": total_tokens
|
|
342
|
-
}
|
|
343
|
-
)
|
|
344
|
-
if hook_results:
|
|
345
|
-
self.logger.info(f"Post-delegation hook executed: {len(hook_results)} hooks")
|
|
346
|
-
# Ticket extraction removed from project
|
|
347
|
-
except Exception as e:
|
|
348
|
-
self.logger.warning(f"Post-delegation hook error: {e}")
|
|
349
|
-
|
|
350
|
-
# Log agent invocation with full details
|
|
351
|
-
self.project_logger.log_agent_invocation(
|
|
352
|
-
agent=agent,
|
|
353
|
-
task=task,
|
|
354
|
-
prompt=prompt,
|
|
355
|
-
response=response,
|
|
356
|
-
execution_time=execution_time,
|
|
357
|
-
tokens=total_tokens,
|
|
358
|
-
success=True,
|
|
359
|
-
metadata={
|
|
360
|
-
"session_id": getattr(self, 'current_session_id', None),
|
|
361
|
-
"delegation_format": "subprocess"
|
|
362
|
-
}
|
|
363
|
-
)
|
|
364
|
-
|
|
365
|
-
return response, execution_time, total_tokens
|
|
366
|
-
else:
|
|
367
|
-
error_msg = f"Subprocess failed: {stderr}"
|
|
368
|
-
if self.log_level != "OFF":
|
|
369
|
-
self.logger.error(f"{agent} subprocess error: {error_msg}")
|
|
370
|
-
return error_msg, execution_time, token_estimate
|
|
371
|
-
|
|
372
|
-
except subprocess.TimeoutExpired:
|
|
373
|
-
execution_time = time.time() - start_time
|
|
374
|
-
error_msg = f"Subprocess timed out after {execution_time:.1f}s"
|
|
375
|
-
if self.log_level != "OFF":
|
|
376
|
-
self.logger.error(f"{agent} {error_msg}")
|
|
377
|
-
|
|
378
|
-
# Log timeout error
|
|
379
|
-
self.project_logger.log_agent_invocation(
|
|
380
|
-
agent=agent,
|
|
381
|
-
task=task,
|
|
382
|
-
prompt=prompt,
|
|
383
|
-
response=error_msg,
|
|
384
|
-
execution_time=execution_time,
|
|
385
|
-
tokens=token_estimate,
|
|
386
|
-
success=False,
|
|
387
|
-
metadata={
|
|
388
|
-
"error": "timeout",
|
|
389
|
-
"delegation_format": "subprocess"
|
|
390
|
-
}
|
|
391
|
-
)
|
|
392
|
-
|
|
393
|
-
return error_msg, execution_time, token_estimate
|
|
394
|
-
except Exception as e:
|
|
395
|
-
execution_time = time.time() - start_time
|
|
396
|
-
error_msg = f"Subprocess error: {str(e)}"
|
|
397
|
-
if self.log_level != "OFF":
|
|
398
|
-
self.logger.error(f"{agent} {error_msg}")
|
|
399
|
-
|
|
400
|
-
# Log exception error
|
|
401
|
-
self.project_logger.log_agent_invocation(
|
|
402
|
-
agent=agent,
|
|
403
|
-
task=task,
|
|
404
|
-
prompt=prompt,
|
|
405
|
-
response=error_msg,
|
|
406
|
-
execution_time=execution_time,
|
|
407
|
-
tokens=token_estimate,
|
|
408
|
-
success=False,
|
|
409
|
-
metadata={
|
|
410
|
-
"error": "exception",
|
|
411
|
-
"exception_type": type(e).__name__,
|
|
412
|
-
"delegation_format": "subprocess"
|
|
413
|
-
}
|
|
414
|
-
)
|
|
415
|
-
|
|
416
|
-
return error_msg, execution_time, token_estimate
|
|
417
|
-
|
|
418
|
-
def _handle_todo_delegation(self, delegation: Dict[str, Any]):
|
|
419
|
-
"""
|
|
420
|
-
Handle a delegation created from a TODO.
|
|
421
|
-
|
|
422
|
-
Args:
|
|
423
|
-
delegation: Delegation dict from TodoHijacker
|
|
424
|
-
"""
|
|
425
|
-
self.logger.info(f"TODO delegation received: {delegation['agent']} - {delegation['task'][:50]}...")
|
|
426
|
-
self._pending_todo_delegations.append(delegation)
|
|
427
|
-
|
|
428
|
-
def _process_pending_todo_delegations(self) -> List[Dict[str, Any]]:
|
|
429
|
-
"""
|
|
430
|
-
Process any pending TODO delegations.
|
|
431
|
-
|
|
432
|
-
Returns:
|
|
433
|
-
List of results from running the delegations
|
|
434
|
-
"""
|
|
435
|
-
if not self._pending_todo_delegations:
|
|
436
|
-
return []
|
|
437
|
-
|
|
438
|
-
self.logger.info(f"Processing {len(self._pending_todo_delegations)} TODO delegations")
|
|
439
|
-
|
|
440
|
-
# Run the delegations
|
|
441
|
-
results = self.run_parallel_tasks(self._pending_todo_delegations)
|
|
442
|
-
|
|
443
|
-
# Clear pending list
|
|
444
|
-
self._pending_todo_delegations.clear()
|
|
445
|
-
|
|
446
|
-
return results
|
|
447
|
-
|
|
448
|
-
def run_parallel_tasks(self, delegations: List[Dict[str, str]]) -> List[Dict[str, Any]]:
|
|
449
|
-
"""
|
|
450
|
-
Run multiple agent tasks in parallel.
|
|
451
|
-
|
|
452
|
-
Args:
|
|
453
|
-
delegations: List of delegation dicts with 'agent' and 'task'
|
|
454
|
-
|
|
455
|
-
Returns:
|
|
456
|
-
List of results with agent, response, timing, and tokens
|
|
457
|
-
"""
|
|
458
|
-
results = []
|
|
459
|
-
|
|
460
|
-
# Use ThreadPoolExecutor for parallel execution
|
|
461
|
-
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
|
|
462
|
-
# Submit all tasks
|
|
463
|
-
future_to_delegation = {}
|
|
464
|
-
for delegation in delegations:
|
|
465
|
-
future = executor.submit(
|
|
466
|
-
self.run_subprocess,
|
|
467
|
-
delegation['agent'],
|
|
468
|
-
delegation['task']
|
|
469
|
-
)
|
|
470
|
-
future_to_delegation[future] = delegation
|
|
471
|
-
|
|
472
|
-
# Collect results as they complete
|
|
473
|
-
for future in concurrent.futures.as_completed(future_to_delegation):
|
|
474
|
-
delegation = future_to_delegation[future]
|
|
475
|
-
try:
|
|
476
|
-
response, exec_time, tokens = future.result()
|
|
477
|
-
results.append({
|
|
478
|
-
'agent': delegation['agent'],
|
|
479
|
-
'task': delegation['task'],
|
|
480
|
-
'response': response,
|
|
481
|
-
'execution_time': exec_time,
|
|
482
|
-
'tokens': tokens,
|
|
483
|
-
'status': 'completed'
|
|
484
|
-
})
|
|
485
|
-
except Exception as e:
|
|
486
|
-
results.append({
|
|
487
|
-
'agent': delegation['agent'],
|
|
488
|
-
'task': delegation['task'],
|
|
489
|
-
'response': str(e),
|
|
490
|
-
'execution_time': 0,
|
|
491
|
-
'tokens': 0,
|
|
492
|
-
'status': 'failed'
|
|
493
|
-
})
|
|
494
|
-
|
|
495
|
-
return results
|
|
496
|
-
|
|
497
|
-
def format_results(self, results: List[Dict[str, Any]]) -> str:
|
|
498
|
-
"""
|
|
499
|
-
Format subprocess results in Claude Task tool style.
|
|
500
|
-
|
|
501
|
-
Args:
|
|
502
|
-
results: List of result dicts
|
|
503
|
-
|
|
504
|
-
Returns:
|
|
505
|
-
Formatted output mimicking Claude's Task tool
|
|
506
|
-
"""
|
|
507
|
-
output = []
|
|
508
|
-
|
|
509
|
-
# Show task executions
|
|
510
|
-
for result in results:
|
|
511
|
-
status_icon = "⏺" if result['status'] == 'completed' else "❌"
|
|
512
|
-
tokens_k = result['tokens'] / 1000
|
|
513
|
-
|
|
514
|
-
output.append(f"{status_icon} Task({result['task'][:50]}...)")
|
|
515
|
-
output.append(f" ⎿ Done (0 tool uses · {tokens_k:.1f}k tokens · {result['execution_time']:.1f}s)")
|
|
516
|
-
output.append("")
|
|
517
|
-
|
|
518
|
-
# Aggregate responses
|
|
519
|
-
output.append("## Agent Responses\n")
|
|
520
|
-
|
|
521
|
-
for result in results:
|
|
522
|
-
# Use colorized agent name for display
|
|
523
|
-
agent_display = agent_name_normalizer.colorize(result['agent'], f"{agent_name_normalizer.normalize(result['agent'])} Agent")
|
|
524
|
-
output.append(f"### {agent_display}")
|
|
525
|
-
output.append(result['response'])
|
|
526
|
-
output.append("")
|
|
527
|
-
|
|
528
|
-
return "\n".join(output)
|
|
529
|
-
|
|
530
|
-
def run_non_interactive(self, user_input: str):
|
|
531
|
-
"""
|
|
532
|
-
Run non-interactive session with subprocess orchestration.
|
|
533
|
-
|
|
534
|
-
This method:
|
|
535
|
-
1. Runs PM with user input
|
|
536
|
-
2. Detects delegations in PM response
|
|
537
|
-
3. Creates subprocesses for each delegation
|
|
538
|
-
4. Aggregates and displays results
|
|
539
|
-
5. Processes any TODO-based delegations if hijacking is enabled
|
|
540
|
-
"""
|
|
541
|
-
try:
|
|
542
|
-
# Log session start
|
|
543
|
-
self.project_logger.log_system(
|
|
544
|
-
f"Starting non-interactive session with input: {user_input[:100]}",
|
|
545
|
-
level="INFO",
|
|
546
|
-
component="orchestrator"
|
|
547
|
-
)
|
|
548
|
-
|
|
549
|
-
# Start TODO monitoring if enabled
|
|
550
|
-
if self.todo_hijacker:
|
|
551
|
-
self.todo_hijacker.start_monitoring()
|
|
552
|
-
# Submit hook for user input
|
|
553
|
-
if self.hook_client:
|
|
554
|
-
try:
|
|
555
|
-
self.logger.info("Calling submit hook for user input")
|
|
556
|
-
hook_results = self.hook_client.execute_submit_hook(
|
|
557
|
-
prompt=user_input,
|
|
558
|
-
session_type="subprocess"
|
|
559
|
-
)
|
|
560
|
-
if hook_results:
|
|
561
|
-
self.logger.info(f"Submit hook executed: {len(hook_results)} hooks")
|
|
562
|
-
except Exception as e:
|
|
563
|
-
self.logger.warning(f"Submit hook error: {e}")
|
|
564
|
-
# Prepare PM prompt with minimal framework
|
|
565
|
-
try:
|
|
566
|
-
from ..core.minimal_framework_loader import MinimalFrameworkLoader
|
|
567
|
-
except ImportError:
|
|
568
|
-
from core.minimal_framework_loader import MinimalFrameworkLoader
|
|
569
|
-
|
|
570
|
-
minimal_loader = MinimalFrameworkLoader(self.framework_loader.framework_path)
|
|
571
|
-
framework = minimal_loader.get_framework_instructions()
|
|
572
|
-
|
|
573
|
-
# Add instruction to use delegation format
|
|
574
|
-
framework += """
|
|
575
|
-
## Delegation Format
|
|
576
|
-
When delegating tasks, use this exact format:
|
|
577
|
-
**[Agent Name]**: [Task description]
|
|
578
|
-
|
|
579
|
-
Example:
|
|
580
|
-
**Engineer**: Create a function that calculates factorial
|
|
581
|
-
**QA**: Write tests for the factorial function
|
|
582
|
-
"""
|
|
583
|
-
|
|
584
|
-
full_message = framework + "\n\nUser: " + user_input
|
|
585
|
-
|
|
586
|
-
if self.log_level != "OFF":
|
|
587
|
-
self.logger.info("Running PM with user input")
|
|
588
|
-
|
|
589
|
-
# Get allowed tools for PM
|
|
590
|
-
pm_allowed_tools = tool_access_control.format_allowed_tools_arg("pm", is_parent=True)
|
|
591
|
-
|
|
592
|
-
if self.log_level != "OFF":
|
|
593
|
-
self.logger.info(f"Applying PM tool restrictions: {pm_allowed_tools}")
|
|
594
|
-
|
|
595
|
-
# Use unified launcher for PM execution with tool restrictions
|
|
596
|
-
# Create command with tool restrictions for PM
|
|
597
|
-
cmd = [self.launcher.claude_path, "--dangerously-skip-permissions"]
|
|
598
|
-
if self.launcher.model:
|
|
599
|
-
cmd.extend(["--model", self.launcher.model])
|
|
600
|
-
|
|
601
|
-
# Restrict PM to only delegation and tracking tools
|
|
602
|
-
cmd.extend(["--allowedTools", pm_allowed_tools])
|
|
603
|
-
|
|
604
|
-
# Use subprocess directly for more control
|
|
605
|
-
import subprocess
|
|
606
|
-
process = subprocess.Popen(
|
|
607
|
-
cmd,
|
|
608
|
-
stdin=subprocess.PIPE,
|
|
609
|
-
stdout=subprocess.PIPE,
|
|
610
|
-
stderr=subprocess.PIPE,
|
|
611
|
-
text=True
|
|
612
|
-
)
|
|
613
|
-
|
|
614
|
-
stdout, stderr = process.communicate(input=full_message, timeout=30)
|
|
615
|
-
returncode = process.returncode
|
|
616
|
-
|
|
617
|
-
if returncode != 0:
|
|
618
|
-
print(f"Error: {stderr}")
|
|
619
|
-
return
|
|
620
|
-
|
|
621
|
-
pm_response = stdout
|
|
622
|
-
print("PM Response:")
|
|
623
|
-
print("-" * 50)
|
|
624
|
-
print(pm_response)
|
|
625
|
-
print("-" * 50)
|
|
626
|
-
|
|
627
|
-
# Log PM response in DEBUG mode
|
|
628
|
-
if self.log_level == "DEBUG":
|
|
629
|
-
self.project_logger.log_system(
|
|
630
|
-
f"PM Response (full): {pm_response}",
|
|
631
|
-
level="DEBUG",
|
|
632
|
-
component="orchestrator"
|
|
633
|
-
)
|
|
634
|
-
# Also save PM response to cache for analysis
|
|
635
|
-
from pathlib import Path
|
|
636
|
-
cache_dir = Path.cwd() / ".claude-mpm" / "cache"
|
|
637
|
-
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
638
|
-
pm_cache_file = cache_dir / f"pm_response_{self.project_logger.session_id}.txt"
|
|
639
|
-
pm_cache_file.write_text(pm_response)
|
|
640
|
-
|
|
641
|
-
# Detect delegations
|
|
642
|
-
delegations = self.detect_delegations(pm_response)
|
|
643
|
-
|
|
644
|
-
# Log delegation detection
|
|
645
|
-
if delegations:
|
|
646
|
-
self.project_logger.log_delegation(
|
|
647
|
-
pm_task=user_input,
|
|
648
|
-
delegations=delegations,
|
|
649
|
-
pm_response=pm_response
|
|
650
|
-
)
|
|
651
|
-
|
|
652
|
-
if delegations:
|
|
653
|
-
print(f"\nDetected {len(delegations)} delegations. Running subprocesses...\n")
|
|
654
|
-
|
|
655
|
-
# Run delegations in parallel
|
|
656
|
-
results = self.run_parallel_tasks(delegations)
|
|
657
|
-
|
|
658
|
-
# Format and display results
|
|
659
|
-
formatted_results = self.format_results(results)
|
|
660
|
-
print(formatted_results)
|
|
661
|
-
|
|
662
|
-
# Ticket extraction removed from project
|
|
663
|
-
else:
|
|
664
|
-
print("\nNo delegations detected in PM response.")
|
|
665
|
-
|
|
666
|
-
# Process any TODO-based delegations
|
|
667
|
-
if self.todo_hijacker:
|
|
668
|
-
# Give a moment for TODO files to be written
|
|
669
|
-
time.sleep(0.5)
|
|
670
|
-
|
|
671
|
-
# Process pending TODO delegations
|
|
672
|
-
todo_results = self._process_pending_todo_delegations()
|
|
673
|
-
if todo_results:
|
|
674
|
-
print(f"\nProcessed {len(todo_results)} TODO-based delegations:")
|
|
675
|
-
formatted_todo_results = self.format_results(todo_results)
|
|
676
|
-
print(formatted_todo_results)
|
|
677
|
-
|
|
678
|
-
# Ticket creation removed from project
|
|
679
|
-
|
|
680
|
-
# Create session report
|
|
681
|
-
self.project_logger.create_session_report()
|
|
682
|
-
|
|
683
|
-
except Exception as e:
|
|
684
|
-
print(f"Error: {e}")
|
|
685
|
-
if self.log_level != "OFF":
|
|
686
|
-
self.logger.error(f"Non-interactive error: {e}")
|
|
687
|
-
finally:
|
|
688
|
-
# Stop TODO monitoring
|
|
689
|
-
if self.todo_hijacker:
|
|
690
|
-
self.todo_hijacker.stop_monitoring()
|
|
691
|
-
|
|
692
|
-
def run_interactive(self):
|
|
693
|
-
"""Run an interactive session with framework instructions."""
|
|
694
|
-
try:
|
|
695
|
-
from .._version import __version__
|
|
696
|
-
except ImportError:
|
|
697
|
-
from claude_mpm._version import __version__
|
|
698
|
-
|
|
699
|
-
print(f"Claude MPM v{__version__} - Interactive Session")
|
|
700
|
-
print("Starting Claude with framework instructions...")
|
|
701
|
-
print("-" * 50)
|
|
702
|
-
|
|
703
|
-
# Get framework instructions
|
|
704
|
-
framework = self.framework_loader.get_framework_instructions()
|
|
705
|
-
|
|
706
|
-
# Submit hook for framework initialization if available
|
|
707
|
-
if self.hook_client:
|
|
708
|
-
hook_response = self.hook_client.execute_submit_hook(
|
|
709
|
-
"Starting interactive session with framework",
|
|
710
|
-
hook_type='framework_init',
|
|
711
|
-
framework=framework,
|
|
712
|
-
session_mode='interactive'
|
|
713
|
-
)
|
|
714
|
-
if hook_response and 'framework' in hook_response:
|
|
715
|
-
framework = hook_response['framework']
|
|
716
|
-
|
|
717
|
-
if self.log_level != "OFF":
|
|
718
|
-
self.logger.info("Starting Claude with framework instructions")
|
|
719
|
-
self.logger.info(f"Framework size: {len(framework)} bytes")
|
|
720
|
-
|
|
721
|
-
print(f"\nFramework version: {self.framework_loader.framework_content.get('version', 'unknown')}")
|
|
722
|
-
print(f"Framework size: {len(framework):,} bytes")
|
|
723
|
-
|
|
724
|
-
# Display agent versions
|
|
725
|
-
print("\nAgent versions (base-agent):")
|
|
726
|
-
try:
|
|
727
|
-
from pathlib import Path
|
|
728
|
-
import json
|
|
729
|
-
agents_dir = Path.cwd() / ".claude" / "agents"
|
|
730
|
-
if agents_dir.exists():
|
|
731
|
-
agent_files = sorted(agents_dir.glob("*.yml"))
|
|
732
|
-
if agent_files:
|
|
733
|
-
for agent_file in agent_files[:8]: # Show up to 8 agents
|
|
734
|
-
try:
|
|
735
|
-
with open(agent_file, 'r') as f:
|
|
736
|
-
for line in f:
|
|
737
|
-
if line.startswith("version:"):
|
|
738
|
-
version = line.split(":", 1)[1].strip().strip('"')
|
|
739
|
-
agent_name = agent_file.stem
|
|
740
|
-
print(f" - {agent_name:<20} {version}")
|
|
741
|
-
break
|
|
742
|
-
except:
|
|
743
|
-
pass
|
|
744
|
-
else:
|
|
745
|
-
print(" No agents deployed")
|
|
746
|
-
else:
|
|
747
|
-
print(" No agents directory found")
|
|
748
|
-
except Exception as e:
|
|
749
|
-
print(f" Error reading agent versions: {e}")
|
|
750
|
-
|
|
751
|
-
print("-" * 50)
|
|
752
|
-
|
|
753
|
-
try:
|
|
754
|
-
import subprocess
|
|
755
|
-
import sys
|
|
756
|
-
|
|
757
|
-
# Build command with --append-system-prompt
|
|
758
|
-
cmd = [self.launcher.claude_path, "--dangerously-skip-permissions"]
|
|
759
|
-
|
|
760
|
-
# Add model if specified
|
|
761
|
-
if self.launcher.model:
|
|
762
|
-
cmd.extend(["--model", self.launcher.model])
|
|
763
|
-
|
|
764
|
-
# Add the framework as system prompt
|
|
765
|
-
cmd.extend(["--append-system-prompt", framework])
|
|
766
|
-
|
|
767
|
-
# Get PM tool restrictions
|
|
768
|
-
pm_allowed_tools = tool_access_control.format_allowed_tools_arg("pm", is_parent=True)
|
|
769
|
-
|
|
770
|
-
# Restrict PM to only delegation and tracking tools
|
|
771
|
-
cmd.extend(["--allowedTools", pm_allowed_tools])
|
|
772
|
-
|
|
773
|
-
if self.log_level != "OFF":
|
|
774
|
-
self.logger.debug(f"Launching Claude with command: {' '.join(cmd[:4])}... (framework omitted)")
|
|
775
|
-
|
|
776
|
-
# Launch Claude directly with inherited stdio
|
|
777
|
-
process = subprocess.Popen(
|
|
778
|
-
cmd,
|
|
779
|
-
stdin=sys.stdin,
|
|
780
|
-
stdout=sys.stdout,
|
|
781
|
-
stderr=sys.stderr,
|
|
782
|
-
text=True
|
|
783
|
-
)
|
|
784
|
-
|
|
785
|
-
# Wait for process to complete
|
|
786
|
-
process.wait()
|
|
787
|
-
|
|
788
|
-
# After session ends, extract and create tickets
|
|
789
|
-
# Note: In interactive mode, we can't capture output directly
|
|
790
|
-
# so ticket extraction would need to be handled differently
|
|
791
|
-
|
|
792
|
-
except KeyboardInterrupt:
|
|
793
|
-
print("\nSession interrupted by user")
|
|
794
|
-
if self.log_level != "OFF":
|
|
795
|
-
self.logger.info("Interactive session interrupted")
|
|
796
|
-
except Exception as e:
|
|
797
|
-
print(f"Error running Claude: {e}")
|
|
798
|
-
if self.log_level != "OFF":
|
|
799
|
-
self.logger.error(f"Interactive error: {e}")
|
|
800
|
-
|
|
801
|
-
# _create_tickets method removed - TicketExtractor functionality removed from project
|