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,252 +0,0 @@
|
|
|
1
|
-
"""Orchestrator using pexpect for proper terminal interaction."""
|
|
2
|
-
|
|
3
|
-
import pexpect
|
|
4
|
-
import sys
|
|
5
|
-
import os
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import Optional
|
|
8
|
-
from datetime import datetime
|
|
9
|
-
import logging
|
|
10
|
-
|
|
11
|
-
try:
|
|
12
|
-
from ..core.logger import get_logger, setup_logging
|
|
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
|
-
# TicketExtractor removed from project
|
|
19
|
-
from core.framework_loader import FrameworkLoader
|
|
20
|
-
from orchestration.agent_delegator import AgentDelegator
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class PexpectOrchestrator:
|
|
24
|
-
"""Orchestrator using pexpect for proper terminal control."""
|
|
25
|
-
|
|
26
|
-
def __init__(
|
|
27
|
-
self,
|
|
28
|
-
framework_path: Optional[Path] = None,
|
|
29
|
-
agents_dir: Optional[Path] = None,
|
|
30
|
-
log_level: str = "OFF",
|
|
31
|
-
log_dir: Optional[Path] = None,
|
|
32
|
-
):
|
|
33
|
-
"""Initialize the orchestrator."""
|
|
34
|
-
self.log_level = log_level
|
|
35
|
-
self.log_dir = log_dir or (Path.home() / ".claude-mpm" / "logs")
|
|
36
|
-
|
|
37
|
-
# Set up logging
|
|
38
|
-
if log_level != "OFF":
|
|
39
|
-
self.logger = setup_logging(level=log_level, log_dir=log_dir)
|
|
40
|
-
self.logger.info(f"Initializing Pexpect Orchestrator (log_level={log_level})")
|
|
41
|
-
else:
|
|
42
|
-
# Minimal logger
|
|
43
|
-
self.logger = get_logger("pexpect_orchestrator")
|
|
44
|
-
self.logger.setLevel(logging.WARNING)
|
|
45
|
-
|
|
46
|
-
# Components
|
|
47
|
-
self.framework_loader = FrameworkLoader(framework_path, agents_dir)
|
|
48
|
-
# TicketExtractor removed from project
|
|
49
|
-
self.agent_delegator = AgentDelegator(self.framework_loader.agent_registry)
|
|
50
|
-
|
|
51
|
-
# State
|
|
52
|
-
self.first_interaction = True
|
|
53
|
-
self.session_start = datetime.now()
|
|
54
|
-
self.session_log = []
|
|
55
|
-
# Ticket creation removed from project
|
|
56
|
-
self.child = None
|
|
57
|
-
|
|
58
|
-
def run_interactive(self):
|
|
59
|
-
"""Run an interactive session using pexpect."""
|
|
60
|
-
print("Claude MPM Interactive Session (Terminal Mode)")
|
|
61
|
-
print("Type '/exit' to end session")
|
|
62
|
-
print("-" * 50)
|
|
63
|
-
|
|
64
|
-
try:
|
|
65
|
-
# Start Claude with pexpect
|
|
66
|
-
self.logger.info("Starting Claude with pexpect")
|
|
67
|
-
self.child = pexpect.spawn(
|
|
68
|
-
'claude',
|
|
69
|
-
['--model', 'opus', '--dangerously-skip-permissions'],
|
|
70
|
-
encoding='utf-8',
|
|
71
|
-
timeout=None,
|
|
72
|
-
dimensions=(24, 80) # Standard terminal size
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
# Set up logging if needed
|
|
76
|
-
if self.log_level == "DEBUG":
|
|
77
|
-
log_file = open(self.log_dir / f"pexpect_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log", 'w')
|
|
78
|
-
self.child.logfile = log_file
|
|
79
|
-
|
|
80
|
-
# Wait for Claude to be ready
|
|
81
|
-
self.logger.info("Waiting for Claude prompt")
|
|
82
|
-
self.child.expect('>', timeout=10)
|
|
83
|
-
|
|
84
|
-
# First interaction - inject framework
|
|
85
|
-
if self.first_interaction:
|
|
86
|
-
self.logger.info("Injecting framework instructions")
|
|
87
|
-
framework = self.framework_loader.get_framework_instructions()
|
|
88
|
-
|
|
89
|
-
# Save prompt if debugging
|
|
90
|
-
if self.log_level == "DEBUG":
|
|
91
|
-
prompt_path = Path.home() / ".claude-mpm" / "prompts"
|
|
92
|
-
prompt_path.mkdir(parents=True, exist_ok=True)
|
|
93
|
-
prompt_file = prompt_path / f"prompt_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
|
|
94
|
-
prompt_file.write_text(framework)
|
|
95
|
-
self.logger.debug(f"Framework saved to: {prompt_file}")
|
|
96
|
-
|
|
97
|
-
# Send framework
|
|
98
|
-
self.child.sendline(framework)
|
|
99
|
-
self.child.expect('>', timeout=60) # Give more time for framework processing
|
|
100
|
-
self.first_interaction = False
|
|
101
|
-
|
|
102
|
-
# Clear the screen after framework injection
|
|
103
|
-
print("\033[2J\033[H") # Clear screen and move cursor to top
|
|
104
|
-
print("Claude MPM Interactive Session")
|
|
105
|
-
print("Type '/exit' to end session")
|
|
106
|
-
print("-" * 50)
|
|
107
|
-
|
|
108
|
-
# Interactive loop
|
|
109
|
-
while True:
|
|
110
|
-
try:
|
|
111
|
-
# Get user input
|
|
112
|
-
user_input = input("\nYou: ")
|
|
113
|
-
|
|
114
|
-
# Check for exit
|
|
115
|
-
if user_input.strip().lower() in ['/exit', 'exit', 'quit']:
|
|
116
|
-
break
|
|
117
|
-
|
|
118
|
-
# Send to Claude
|
|
119
|
-
self.child.sendline(user_input)
|
|
120
|
-
|
|
121
|
-
# Capture response
|
|
122
|
-
print("\nClaude: ", end='', flush=True)
|
|
123
|
-
self.child.expect('>', timeout=120) # 2 minute timeout for responses
|
|
124
|
-
|
|
125
|
-
# Get the response (everything before the prompt)
|
|
126
|
-
response = self.child.before
|
|
127
|
-
|
|
128
|
-
# Process response for tickets
|
|
129
|
-
for line in response.split('\n'):
|
|
130
|
-
# Ticket extraction removed from project
|
|
131
|
-
|
|
132
|
-
# Extract agent delegations
|
|
133
|
-
delegations = self.agent_delegator.extract_delegations(line)
|
|
134
|
-
for delegation in delegations:
|
|
135
|
-
if self.log_level != "OFF":
|
|
136
|
-
self.logger.info(f"Detected delegation to {delegation['agent']}: {delegation['task']}")
|
|
137
|
-
|
|
138
|
-
# Display response
|
|
139
|
-
print(response)
|
|
140
|
-
|
|
141
|
-
# Log interaction
|
|
142
|
-
self._log_interaction("input", user_input)
|
|
143
|
-
self._log_interaction("output", response)
|
|
144
|
-
|
|
145
|
-
except pexpect.TIMEOUT:
|
|
146
|
-
print("\n[Timeout waiting for response]")
|
|
147
|
-
self.logger.warning("Response timeout")
|
|
148
|
-
except KeyboardInterrupt:
|
|
149
|
-
print("\n[Interrupted]")
|
|
150
|
-
break
|
|
151
|
-
except Exception as e:
|
|
152
|
-
print(f"\n[Error: {e}]")
|
|
153
|
-
self.logger.error(f"Interaction error: {e}")
|
|
154
|
-
|
|
155
|
-
except Exception as e:
|
|
156
|
-
print(f"\nFailed to start Claude: {e}")
|
|
157
|
-
self.logger.error(f"Failed to start Claude: {e}")
|
|
158
|
-
finally:
|
|
159
|
-
self.stop()
|
|
160
|
-
|
|
161
|
-
def _log_interaction(self, interaction_type: str, content: str):
|
|
162
|
-
"""Log interaction for session history."""
|
|
163
|
-
self.session_log.append({
|
|
164
|
-
"type": interaction_type,
|
|
165
|
-
"content": content,
|
|
166
|
-
"timestamp": datetime.now().isoformat()
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
def stop(self):
|
|
170
|
-
"""Stop the orchestrated session."""
|
|
171
|
-
self.logger.info("Stopping pexpect orchestrator")
|
|
172
|
-
|
|
173
|
-
# Close pexpect child
|
|
174
|
-
if self.child:
|
|
175
|
-
try:
|
|
176
|
-
self.child.sendline('/exit')
|
|
177
|
-
self.child.expect(pexpect.EOF, timeout=5)
|
|
178
|
-
except:
|
|
179
|
-
pass
|
|
180
|
-
finally:
|
|
181
|
-
self.child.close()
|
|
182
|
-
|
|
183
|
-
# Save session log
|
|
184
|
-
self._save_session_log()
|
|
185
|
-
|
|
186
|
-
# Create tickets
|
|
187
|
-
# Ticket creation removed from project
|
|
188
|
-
|
|
189
|
-
print("\nSession ended")
|
|
190
|
-
|
|
191
|
-
def _save_session_log(self):
|
|
192
|
-
"""Save session log to file."""
|
|
193
|
-
try:
|
|
194
|
-
log_dir = Path.home() / ".claude-mpm" / "sessions"
|
|
195
|
-
log_dir.mkdir(parents=True, exist_ok=True)
|
|
196
|
-
|
|
197
|
-
timestamp = self.session_start.strftime("%Y%m%d_%H%M%S")
|
|
198
|
-
log_file = log_dir / f"session_{timestamp}.json"
|
|
199
|
-
|
|
200
|
-
import json
|
|
201
|
-
session_data = {
|
|
202
|
-
"session_start": self.session_start.isoformat(),
|
|
203
|
-
"session_end": datetime.now().isoformat(),
|
|
204
|
-
"interactions": self.session_log,
|
|
205
|
-
# Ticket extraction removed from project
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
with open(log_file, 'w') as f:
|
|
209
|
-
json.dump(session_data, f, indent=2)
|
|
210
|
-
|
|
211
|
-
if self.log_level != "OFF":
|
|
212
|
-
self.logger.info(f"Session log saved to: {log_file}")
|
|
213
|
-
|
|
214
|
-
except Exception as e:
|
|
215
|
-
self.logger.error(f"Failed to save session log: {e}")
|
|
216
|
-
|
|
217
|
-
# _create_tickets method removed - TicketExtractor functionality removed from project
|
|
218
|
-
|
|
219
|
-
def run_non_interactive(self, user_input: str):
|
|
220
|
-
"""Run a non-interactive session using print mode."""
|
|
221
|
-
# For non-interactive, fall back to simple print mode
|
|
222
|
-
try:
|
|
223
|
-
# Prepare message with framework
|
|
224
|
-
framework = self.framework_loader.get_framework_instructions()
|
|
225
|
-
full_message = framework + "\n\nUser: " + user_input
|
|
226
|
-
|
|
227
|
-
# Build command
|
|
228
|
-
cmd = [
|
|
229
|
-
"claude",
|
|
230
|
-
"--model", "opus",
|
|
231
|
-
"--dangerously-skip-permissions",
|
|
232
|
-
"--print", # Print mode
|
|
233
|
-
full_message
|
|
234
|
-
]
|
|
235
|
-
|
|
236
|
-
# Run Claude
|
|
237
|
-
import subprocess
|
|
238
|
-
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
239
|
-
|
|
240
|
-
if result.returncode == 0:
|
|
241
|
-
print(result.stdout)
|
|
242
|
-
|
|
243
|
-
# Ticket extraction removed from project
|
|
244
|
-
else:
|
|
245
|
-
print(f"Error: {result.stderr}")
|
|
246
|
-
|
|
247
|
-
# Create tickets
|
|
248
|
-
# Ticket creation removed from project
|
|
249
|
-
|
|
250
|
-
except Exception as e:
|
|
251
|
-
print(f"Error: {e}")
|
|
252
|
-
self.logger.error(f"Non-interactive error: {e}")
|
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
"""Orchestrator using built-in pty module for terminal interaction."""
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
import pty
|
|
5
|
-
import select
|
|
6
|
-
import subprocess
|
|
7
|
-
import sys
|
|
8
|
-
import termios
|
|
9
|
-
import tty
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
from typing import Optional
|
|
12
|
-
from datetime import datetime
|
|
13
|
-
import logging
|
|
14
|
-
import threading
|
|
15
|
-
import fcntl
|
|
16
|
-
|
|
17
|
-
try:
|
|
18
|
-
from ..core.logger import get_logger, setup_logging
|
|
19
|
-
from ..utils.subprocess_runner import SubprocessRunner, OutputMode
|
|
20
|
-
# TicketExtractor removed from project
|
|
21
|
-
from ..core.framework_loader import FrameworkLoader
|
|
22
|
-
from .agent_delegator import AgentDelegator
|
|
23
|
-
except ImportError:
|
|
24
|
-
from core.logger import get_logger, setup_logging
|
|
25
|
-
from utils.subprocess_runner import SubprocessRunner, OutputMode
|
|
26
|
-
# TicketExtractor removed from project
|
|
27
|
-
from core.framework_loader import FrameworkLoader
|
|
28
|
-
from orchestration.agent_delegator import AgentDelegator
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class PTYOrchestrator:
|
|
32
|
-
"""Orchestrator using built-in pty module for proper terminal control."""
|
|
33
|
-
|
|
34
|
-
def __init__(
|
|
35
|
-
self,
|
|
36
|
-
framework_path: Optional[Path] = None,
|
|
37
|
-
agents_dir: Optional[Path] = None,
|
|
38
|
-
log_level: str = "OFF",
|
|
39
|
-
log_dir: Optional[Path] = None,
|
|
40
|
-
):
|
|
41
|
-
"""Initialize the orchestrator."""
|
|
42
|
-
self.log_level = log_level
|
|
43
|
-
self.log_dir = log_dir or (Path.home() / ".claude-mpm" / "logs")
|
|
44
|
-
|
|
45
|
-
# Set up logging
|
|
46
|
-
if log_level != "OFF":
|
|
47
|
-
self.logger = setup_logging(level=log_level, log_dir=log_dir)
|
|
48
|
-
self.logger.info(f"Initializing PTY Orchestrator (log_level={log_level})")
|
|
49
|
-
else:
|
|
50
|
-
# Minimal logger
|
|
51
|
-
self.logger = get_logger("pty_orchestrator")
|
|
52
|
-
self.logger.setLevel(logging.WARNING)
|
|
53
|
-
|
|
54
|
-
# Components
|
|
55
|
-
self.framework_loader = FrameworkLoader(framework_path, agents_dir)
|
|
56
|
-
# TicketExtractor removed from project
|
|
57
|
-
self.agent_delegator = AgentDelegator(self.framework_loader.agent_registry)
|
|
58
|
-
|
|
59
|
-
# State
|
|
60
|
-
self.first_interaction = True
|
|
61
|
-
self.session_start = datetime.now()
|
|
62
|
-
self.session_log = []
|
|
63
|
-
# Ticket creation removed from project
|
|
64
|
-
self.process = None
|
|
65
|
-
self.master_fd = None
|
|
66
|
-
|
|
67
|
-
# Initialize subprocess runner
|
|
68
|
-
self.subprocess_runner = SubprocessRunner(logger=self.logger)
|
|
69
|
-
|
|
70
|
-
def run_interactive(self):
|
|
71
|
-
"""Run an interactive session using pty."""
|
|
72
|
-
print("Claude MPM Interactive Session")
|
|
73
|
-
print("Type 'exit' or 'quit' to end session")
|
|
74
|
-
print("-" * 50)
|
|
75
|
-
|
|
76
|
-
# Save terminal settings
|
|
77
|
-
old_tty = termios.tcgetattr(sys.stdin)
|
|
78
|
-
|
|
79
|
-
try:
|
|
80
|
-
# Create a pseudo-terminal
|
|
81
|
-
master, slave = pty.openpty()
|
|
82
|
-
self.master_fd = master
|
|
83
|
-
|
|
84
|
-
# Start Claude process
|
|
85
|
-
self.logger.info("Starting Claude with pty")
|
|
86
|
-
self.process = subprocess.Popen(
|
|
87
|
-
['claude', '--model', 'opus', '--dangerously-skip-permissions'],
|
|
88
|
-
stdin=slave,
|
|
89
|
-
stdout=slave,
|
|
90
|
-
stderr=slave,
|
|
91
|
-
preexec_fn=os.setsid
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
# Close slave fd in parent
|
|
95
|
-
os.close(slave)
|
|
96
|
-
|
|
97
|
-
# Set non-blocking
|
|
98
|
-
fcntl.fcntl(master, fcntl.F_SETFL, os.O_NONBLOCK)
|
|
99
|
-
|
|
100
|
-
# Put terminal in raw mode
|
|
101
|
-
tty.setraw(sys.stdin.fileno())
|
|
102
|
-
|
|
103
|
-
# Framework injection flag
|
|
104
|
-
framework_injected = False
|
|
105
|
-
framework_buffer = []
|
|
106
|
-
|
|
107
|
-
# I/O loop
|
|
108
|
-
while True:
|
|
109
|
-
try:
|
|
110
|
-
# Check if process is still alive
|
|
111
|
-
if self.process.poll() is not None:
|
|
112
|
-
break
|
|
113
|
-
|
|
114
|
-
# Use select to check for available data
|
|
115
|
-
r, w, e = select.select([sys.stdin, master], [], [], 0.1)
|
|
116
|
-
|
|
117
|
-
# Handle input from user
|
|
118
|
-
if sys.stdin in r:
|
|
119
|
-
data = os.read(sys.stdin.fileno(), 1024)
|
|
120
|
-
|
|
121
|
-
# Check for exit
|
|
122
|
-
if data == b'\x03' or data == b'\x04': # Ctrl+C or Ctrl+D
|
|
123
|
-
break
|
|
124
|
-
|
|
125
|
-
# Inject framework on first real input (after initial Claude startup)
|
|
126
|
-
if not framework_injected and data.strip() and not data.startswith(b'\x1b'):
|
|
127
|
-
self.logger.info("Injecting framework instructions")
|
|
128
|
-
framework = self.framework_loader.get_framework_instructions()
|
|
129
|
-
|
|
130
|
-
# Save prompt if debugging
|
|
131
|
-
if self.log_level == "DEBUG":
|
|
132
|
-
prompt_path = Path.home() / ".claude-mpm" / "prompts"
|
|
133
|
-
prompt_path.mkdir(parents=True, exist_ok=True)
|
|
134
|
-
prompt_file = prompt_path / f"prompt_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
|
|
135
|
-
prompt_file.write_text(framework)
|
|
136
|
-
self.logger.debug(f"Framework saved to: {prompt_file}")
|
|
137
|
-
|
|
138
|
-
# Send framework first
|
|
139
|
-
os.write(master, framework.encode('utf-8') + b'\n')
|
|
140
|
-
framework_injected = True
|
|
141
|
-
|
|
142
|
-
# Then send user input
|
|
143
|
-
os.write(master, data)
|
|
144
|
-
else:
|
|
145
|
-
# Normal input
|
|
146
|
-
os.write(master, data)
|
|
147
|
-
|
|
148
|
-
# Handle output from Claude
|
|
149
|
-
if master in r:
|
|
150
|
-
try:
|
|
151
|
-
data = os.read(master, 1024)
|
|
152
|
-
if data:
|
|
153
|
-
# Write to stdout
|
|
154
|
-
sys.stdout.write(data.decode('utf-8', errors='replace'))
|
|
155
|
-
sys.stdout.flush()
|
|
156
|
-
|
|
157
|
-
# Process for tickets
|
|
158
|
-
text = data.decode('utf-8', errors='replace')
|
|
159
|
-
for line in text.split('\n'):
|
|
160
|
-
# Ticket extraction removed from project
|
|
161
|
-
|
|
162
|
-
# Extract agent delegations
|
|
163
|
-
delegations = self.agent_delegator.extract_delegations(line)
|
|
164
|
-
for delegation in delegations:
|
|
165
|
-
if self.log_level != "OFF":
|
|
166
|
-
self.logger.info(f"Detected delegation to {delegation['agent']}: {delegation['task']}")
|
|
167
|
-
except OSError:
|
|
168
|
-
# No data available
|
|
169
|
-
pass
|
|
170
|
-
|
|
171
|
-
except KeyboardInterrupt:
|
|
172
|
-
break
|
|
173
|
-
except Exception as e:
|
|
174
|
-
self.logger.error(f"Error in I/O loop: {e}")
|
|
175
|
-
break
|
|
176
|
-
|
|
177
|
-
finally:
|
|
178
|
-
# Restore terminal settings
|
|
179
|
-
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
|
|
180
|
-
|
|
181
|
-
# Clean up
|
|
182
|
-
self.stop()
|
|
183
|
-
|
|
184
|
-
def stop(self):
|
|
185
|
-
"""Stop the orchestrated session."""
|
|
186
|
-
self.logger.info("Stopping PTY orchestrator")
|
|
187
|
-
|
|
188
|
-
# Terminate process
|
|
189
|
-
if self.process:
|
|
190
|
-
try:
|
|
191
|
-
self.process.terminate()
|
|
192
|
-
self.process.wait(timeout=5)
|
|
193
|
-
except:
|
|
194
|
-
self.process.kill()
|
|
195
|
-
self.process.wait()
|
|
196
|
-
|
|
197
|
-
# Close master fd
|
|
198
|
-
if self.master_fd:
|
|
199
|
-
try:
|
|
200
|
-
os.close(self.master_fd)
|
|
201
|
-
except:
|
|
202
|
-
pass
|
|
203
|
-
|
|
204
|
-
# Save session log
|
|
205
|
-
self._save_session_log()
|
|
206
|
-
|
|
207
|
-
# Ticket creation removed from project
|
|
208
|
-
|
|
209
|
-
print("\n\nSession ended")
|
|
210
|
-
|
|
211
|
-
def _save_session_log(self):
|
|
212
|
-
"""Save session log to file."""
|
|
213
|
-
try:
|
|
214
|
-
log_dir = Path.home() / ".claude-mpm" / "sessions"
|
|
215
|
-
log_dir.mkdir(parents=True, exist_ok=True)
|
|
216
|
-
|
|
217
|
-
timestamp = self.session_start.strftime("%Y%m%d_%H%M%S")
|
|
218
|
-
log_file = log_dir / f"session_{timestamp}.json"
|
|
219
|
-
|
|
220
|
-
import json
|
|
221
|
-
session_data = {
|
|
222
|
-
"session_start": self.session_start.isoformat(),
|
|
223
|
-
"session_end": datetime.now().isoformat(),
|
|
224
|
-
"interactions": self.session_log,
|
|
225
|
-
# Ticket extraction removed from project
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
with open(log_file, 'w') as f:
|
|
229
|
-
json.dump(session_data, f, indent=2)
|
|
230
|
-
|
|
231
|
-
if self.log_level != "OFF":
|
|
232
|
-
self.logger.info(f"Session log saved to: {log_file}")
|
|
233
|
-
|
|
234
|
-
except Exception as e:
|
|
235
|
-
self.logger.error(f"Failed to save session log: {e}")
|
|
236
|
-
|
|
237
|
-
# _create_tickets method removed - TicketExtractor functionality removed from project
|
|
238
|
-
|
|
239
|
-
def run_non_interactive(self, user_input: str):
|
|
240
|
-
"""Run a non-interactive session using print mode."""
|
|
241
|
-
# For non-interactive, fall back to simple print mode
|
|
242
|
-
try:
|
|
243
|
-
# Prepare message with framework
|
|
244
|
-
framework = self.framework_loader.get_framework_instructions()
|
|
245
|
-
full_message = framework + "\n\nUser: " + user_input
|
|
246
|
-
|
|
247
|
-
# Build command
|
|
248
|
-
cmd = [
|
|
249
|
-
"claude",
|
|
250
|
-
"--model", "opus",
|
|
251
|
-
"--dangerously-skip-permissions",
|
|
252
|
-
"--print", # Print mode
|
|
253
|
-
full_message
|
|
254
|
-
]
|
|
255
|
-
|
|
256
|
-
# Run Claude
|
|
257
|
-
result = self.subprocess_runner.run(cmd)
|
|
258
|
-
|
|
259
|
-
if result.success:
|
|
260
|
-
print(result.stdout)
|
|
261
|
-
|
|
262
|
-
# Ticket extraction removed from project
|
|
263
|
-
else:
|
|
264
|
-
print(f"Error: {result.stderr}")
|
|
265
|
-
|
|
266
|
-
# Ticket creation removed from project
|
|
267
|
-
|
|
268
|
-
except Exception as e:
|
|
269
|
-
print(f"Error: {e}")
|
|
270
|
-
self.logger.error(f"Non-interactive error: {e}")
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
"""Simple orchestrator using Claude's print mode."""
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import Optional
|
|
6
|
-
from datetime import datetime
|
|
7
|
-
|
|
8
|
-
try:
|
|
9
|
-
from ..utils.subprocess_runner import SubprocessRunner
|
|
10
|
-
except ImportError:
|
|
11
|
-
from utils.subprocess_runner import SubprocessRunner
|
|
12
|
-
|
|
13
|
-
class SimpleOrchestrator:
|
|
14
|
-
"""Orchestrator that uses Claude's print mode for each interaction."""
|
|
15
|
-
|
|
16
|
-
def __init__(self, framework_loader, log_level="OFF"):
|
|
17
|
-
self.framework_loader = framework_loader
|
|
18
|
-
self.log_level = log_level
|
|
19
|
-
self.conversation_id = None
|
|
20
|
-
self.framework_injected = False
|
|
21
|
-
self.subprocess_runner = SubprocessRunner()
|
|
22
|
-
|
|
23
|
-
def send_message(self, message: str) -> str:
|
|
24
|
-
"""Send a message to Claude and get response."""
|
|
25
|
-
|
|
26
|
-
# Prepare the full message
|
|
27
|
-
if not self.framework_injected:
|
|
28
|
-
# First message includes framework
|
|
29
|
-
framework = self.framework_loader.get_framework_instructions()
|
|
30
|
-
full_message = framework + "\n\nUser: " + message
|
|
31
|
-
self.framework_injected = True
|
|
32
|
-
else:
|
|
33
|
-
full_message = message
|
|
34
|
-
|
|
35
|
-
# Build command
|
|
36
|
-
cmd = [
|
|
37
|
-
"claude",
|
|
38
|
-
"--model", "opus",
|
|
39
|
-
"--dangerously-skip-permissions",
|
|
40
|
-
"--print", # Print mode
|
|
41
|
-
full_message
|
|
42
|
-
]
|
|
43
|
-
|
|
44
|
-
# If we have a conversation ID, continue it
|
|
45
|
-
if self.conversation_id:
|
|
46
|
-
cmd.extend(["--conversation", self.conversation_id])
|
|
47
|
-
|
|
48
|
-
# Run Claude
|
|
49
|
-
result = self.subprocess_runner.run(cmd)
|
|
50
|
-
|
|
51
|
-
if not result.success:
|
|
52
|
-
raise Exception(f"Claude failed: {result.stderr}")
|
|
53
|
-
|
|
54
|
-
# Extract conversation ID from output if needed
|
|
55
|
-
# (Claude might output this in stderr or in a specific format)
|
|
56
|
-
|
|
57
|
-
return result.stdout
|
|
58
|
-
|
|
59
|
-
def run_interactive(self):
|
|
60
|
-
"""Run interactive session using multiple print calls."""
|
|
61
|
-
print("Claude MPM Session (Print Mode)")
|
|
62
|
-
print("Type 'exit' to quit")
|
|
63
|
-
print("-" * 50)
|
|
64
|
-
|
|
65
|
-
while True:
|
|
66
|
-
try:
|
|
67
|
-
user_input = input("\nYou: ").strip()
|
|
68
|
-
if user_input.lower() in ['exit', 'quit']:
|
|
69
|
-
break
|
|
70
|
-
|
|
71
|
-
if user_input:
|
|
72
|
-
print("\nClaude: ", end='', flush=True)
|
|
73
|
-
response = self.send_message(user_input)
|
|
74
|
-
print(response)
|
|
75
|
-
|
|
76
|
-
except KeyboardInterrupt:
|
|
77
|
-
break
|
|
78
|
-
except Exception as e:
|
|
79
|
-
print(f"\nError: {e}")
|
|
80
|
-
break
|
|
81
|
-
|
|
82
|
-
print("\nSession ended")
|