claude-mpm 3.4.27__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.
Files changed (123) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +182 -299
  3. claude_mpm/agents/agent_loader.py +283 -57
  4. claude_mpm/agents/agent_loader_integration.py +6 -9
  5. claude_mpm/agents/base_agent.json +2 -1
  6. claude_mpm/agents/base_agent_loader.py +1 -1
  7. claude_mpm/cli/__init__.py +5 -7
  8. claude_mpm/cli/commands/__init__.py +0 -2
  9. claude_mpm/cli/commands/agents.py +1 -1
  10. claude_mpm/cli/commands/memory.py +1 -1
  11. claude_mpm/cli/commands/run.py +12 -0
  12. claude_mpm/cli/parser.py +0 -13
  13. claude_mpm/cli/utils.py +1 -1
  14. claude_mpm/config/__init__.py +44 -2
  15. claude_mpm/config/agent_config.py +348 -0
  16. claude_mpm/config/paths.py +322 -0
  17. claude_mpm/constants.py +0 -1
  18. claude_mpm/core/__init__.py +2 -5
  19. claude_mpm/core/agent_registry.py +63 -17
  20. claude_mpm/core/claude_runner.py +354 -43
  21. claude_mpm/core/config.py +7 -1
  22. claude_mpm/core/config_aliases.py +4 -3
  23. claude_mpm/core/config_paths.py +151 -0
  24. claude_mpm/core/factories.py +4 -50
  25. claude_mpm/core/logger.py +11 -13
  26. claude_mpm/core/service_registry.py +2 -2
  27. claude_mpm/dashboard/static/js/components/agent-inference.js +101 -25
  28. claude_mpm/dashboard/static/js/components/event-processor.js +3 -2
  29. claude_mpm/hooks/claude_hooks/hook_handler.py +343 -83
  30. claude_mpm/hooks/memory_integration_hook.py +1 -1
  31. claude_mpm/init.py +37 -6
  32. claude_mpm/scripts/socketio_daemon.py +6 -2
  33. claude_mpm/services/__init__.py +71 -3
  34. claude_mpm/services/agents/__init__.py +85 -0
  35. claude_mpm/services/agents/deployment/__init__.py +21 -0
  36. claude_mpm/services/{agent_deployment.py → agents/deployment/agent_deployment.py} +192 -41
  37. claude_mpm/services/{agent_lifecycle_manager.py → agents/deployment/agent_lifecycle_manager.py} +11 -10
  38. claude_mpm/services/agents/loading/__init__.py +11 -0
  39. claude_mpm/services/{agent_profile_loader.py → agents/loading/agent_profile_loader.py} +9 -8
  40. claude_mpm/services/{base_agent_manager.py → agents/loading/base_agent_manager.py} +2 -2
  41. claude_mpm/services/{framework_agent_loader.py → agents/loading/framework_agent_loader.py} +116 -40
  42. claude_mpm/services/agents/management/__init__.py +9 -0
  43. claude_mpm/services/{agent_management_service.py → agents/management/agent_management_service.py} +6 -5
  44. claude_mpm/services/agents/memory/__init__.py +21 -0
  45. claude_mpm/services/{agent_memory_manager.py → agents/memory/agent_memory_manager.py} +3 -3
  46. claude_mpm/services/agents/registry/__init__.py +29 -0
  47. claude_mpm/services/{agent_registry.py → agents/registry/agent_registry.py} +101 -16
  48. claude_mpm/services/{deployed_agent_discovery.py → agents/registry/deployed_agent_discovery.py} +12 -2
  49. claude_mpm/services/{agent_modification_tracker.py → agents/registry/modification_tracker.py} +6 -5
  50. claude_mpm/services/async_session_logger.py +584 -0
  51. claude_mpm/services/claude_session_logger.py +299 -0
  52. claude_mpm/services/framework_claude_md_generator/content_assembler.py +2 -2
  53. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +17 -17
  54. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +3 -3
  55. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +1 -1
  56. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +1 -1
  57. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +19 -24
  58. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +1 -1
  59. claude_mpm/services/framework_claude_md_generator.py +4 -2
  60. claude_mpm/services/memory/__init__.py +17 -0
  61. claude_mpm/services/{memory_builder.py → memory/builder.py} +3 -3
  62. claude_mpm/services/memory/cache/__init__.py +14 -0
  63. claude_mpm/services/{shared_prompt_cache.py → memory/cache/shared_prompt_cache.py} +1 -1
  64. claude_mpm/services/memory/cache/simple_cache.py +317 -0
  65. claude_mpm/services/{memory_optimizer.py → memory/optimizer.py} +1 -1
  66. claude_mpm/services/{memory_router.py → memory/router.py} +1 -1
  67. claude_mpm/services/optimized_hook_service.py +542 -0
  68. claude_mpm/services/project_registry.py +14 -8
  69. claude_mpm/services/response_tracker.py +237 -0
  70. claude_mpm/services/ticketing_service_original.py +4 -2
  71. claude_mpm/services/version_control/branch_strategy.py +3 -1
  72. claude_mpm/utils/paths.py +12 -10
  73. claude_mpm/utils/session_logging.py +114 -0
  74. claude_mpm/validation/agent_validator.py +2 -1
  75. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/METADATA +26 -20
  76. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/RECORD +83 -106
  77. claude_mpm/cli/commands/ui.py +0 -57
  78. claude_mpm/core/simple_runner.py +0 -1046
  79. claude_mpm/hooks/builtin/__init__.py +0 -1
  80. claude_mpm/hooks/builtin/logging_hook_example.py +0 -165
  81. claude_mpm/hooks/builtin/memory_hooks_example.py +0 -67
  82. claude_mpm/hooks/builtin/mpm_command_hook.py +0 -125
  83. claude_mpm/hooks/builtin/post_delegation_hook_example.py +0 -124
  84. claude_mpm/hooks/builtin/pre_delegation_hook_example.py +0 -125
  85. claude_mpm/hooks/builtin/submit_hook_example.py +0 -100
  86. claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +0 -237
  87. claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +0 -240
  88. claude_mpm/hooks/builtin/workflow_start_hook.py +0 -181
  89. claude_mpm/orchestration/__init__.py +0 -6
  90. claude_mpm/orchestration/archive/direct_orchestrator.py +0 -195
  91. claude_mpm/orchestration/archive/factory.py +0 -215
  92. claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +0 -188
  93. claude_mpm/orchestration/archive/hook_integration_example.py +0 -178
  94. claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +0 -826
  95. claude_mpm/orchestration/archive/orchestrator.py +0 -501
  96. claude_mpm/orchestration/archive/pexpect_orchestrator.py +0 -252
  97. claude_mpm/orchestration/archive/pty_orchestrator.py +0 -270
  98. claude_mpm/orchestration/archive/simple_orchestrator.py +0 -82
  99. claude_mpm/orchestration/archive/subprocess_orchestrator.py +0 -801
  100. claude_mpm/orchestration/archive/system_prompt_orchestrator.py +0 -278
  101. claude_mpm/orchestration/archive/wrapper_orchestrator.py +0 -187
  102. claude_mpm/schemas/workflow_validator.py +0 -411
  103. claude_mpm/services/parent_directory_manager/__init__.py +0 -577
  104. claude_mpm/services/parent_directory_manager/backup_manager.py +0 -258
  105. claude_mpm/services/parent_directory_manager/config_manager.py +0 -210
  106. claude_mpm/services/parent_directory_manager/deduplication_manager.py +0 -279
  107. claude_mpm/services/parent_directory_manager/framework_protector.py +0 -143
  108. claude_mpm/services/parent_directory_manager/operations.py +0 -186
  109. claude_mpm/services/parent_directory_manager/state_manager.py +0 -624
  110. claude_mpm/services/parent_directory_manager/template_deployer.py +0 -579
  111. claude_mpm/services/parent_directory_manager/validation_manager.py +0 -378
  112. claude_mpm/services/parent_directory_manager/version_control_helper.py +0 -339
  113. claude_mpm/services/parent_directory_manager/version_manager.py +0 -222
  114. claude_mpm/ui/__init__.py +0 -1
  115. claude_mpm/ui/rich_terminal_ui.py +0 -295
  116. claude_mpm/ui/terminal_ui.py +0 -328
  117. /claude_mpm/services/{agent_versioning.py → agents/deployment/agent_versioning.py} +0 -0
  118. /claude_mpm/services/{agent_capabilities_generator.py → agents/management/agent_capabilities_generator.py} +0 -0
  119. /claude_mpm/services/{agent_persistence_service.py → agents/memory/agent_persistence_service.py} +0 -0
  120. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/WHEEL +0 -0
  121. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/entry_points.txt +0 -0
  122. {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/licenses/LICENSE +0 -0
  123. {claude_mpm-3.4.27.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")