claude-mpm 3.1.3__py3-none-any.whl → 3.3.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 (80) hide show
  1. claude_mpm/__init__.py +3 -3
  2. claude_mpm/__main__.py +0 -17
  3. claude_mpm/agents/INSTRUCTIONS.md +149 -17
  4. claude_mpm/agents/backups/INSTRUCTIONS.md +238 -0
  5. claude_mpm/agents/base_agent.json +1 -1
  6. claude_mpm/agents/templates/pm.json +25 -0
  7. claude_mpm/agents/templates/research.json +2 -1
  8. claude_mpm/cli/__init__.py +19 -23
  9. claude_mpm/cli/commands/__init__.py +3 -1
  10. claude_mpm/cli/commands/agents.py +7 -18
  11. claude_mpm/cli/commands/info.py +5 -10
  12. claude_mpm/cli/commands/memory.py +232 -0
  13. claude_mpm/cli/commands/run.py +501 -28
  14. claude_mpm/cli/commands/tickets.py +10 -17
  15. claude_mpm/cli/commands/ui.py +15 -37
  16. claude_mpm/cli/parser.py +91 -1
  17. claude_mpm/cli/utils.py +9 -28
  18. claude_mpm/config/socketio_config.py +256 -0
  19. claude_mpm/constants.py +9 -0
  20. claude_mpm/core/__init__.py +2 -2
  21. claude_mpm/core/agent_registry.py +4 -4
  22. claude_mpm/core/claude_runner.py +919 -0
  23. claude_mpm/core/config.py +21 -1
  24. claude_mpm/core/factories.py +1 -1
  25. claude_mpm/core/hook_manager.py +196 -0
  26. claude_mpm/core/pm_hook_interceptor.py +205 -0
  27. claude_mpm/core/service_registry.py +1 -1
  28. claude_mpm/core/simple_runner.py +323 -33
  29. claude_mpm/core/socketio_pool.py +582 -0
  30. claude_mpm/core/websocket_handler.py +233 -0
  31. claude_mpm/deployment_paths.py +261 -0
  32. claude_mpm/hooks/builtin/memory_hooks_example.py +67 -0
  33. claude_mpm/hooks/claude_hooks/hook_handler.py +667 -679
  34. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +9 -4
  35. claude_mpm/hooks/memory_integration_hook.py +312 -0
  36. claude_mpm/models/__init__.py +9 -91
  37. claude_mpm/orchestration/__init__.py +1 -1
  38. claude_mpm/scripts/claude-mpm-socketio +32 -0
  39. claude_mpm/scripts/claude_mpm_monitor.html +567 -0
  40. claude_mpm/scripts/install_socketio_server.py +407 -0
  41. claude_mpm/scripts/launch_monitor.py +132 -0
  42. claude_mpm/scripts/launch_socketio_dashboard.py +261 -0
  43. claude_mpm/scripts/manage_version.py +479 -0
  44. claude_mpm/scripts/socketio_daemon.py +181 -0
  45. claude_mpm/scripts/socketio_server_manager.py +428 -0
  46. claude_mpm/services/__init__.py +5 -0
  47. claude_mpm/services/agent_lifecycle_manager.py +76 -25
  48. claude_mpm/services/agent_memory_manager.py +684 -0
  49. claude_mpm/services/agent_modification_tracker.py +98 -17
  50. claude_mpm/services/agent_persistence_service.py +33 -13
  51. claude_mpm/services/agent_registry.py +82 -43
  52. claude_mpm/services/hook_service.py +362 -0
  53. claude_mpm/services/socketio_client_manager.py +474 -0
  54. claude_mpm/services/socketio_server.py +922 -0
  55. claude_mpm/services/standalone_socketio_server.py +631 -0
  56. claude_mpm/services/ticket_manager.py +4 -5
  57. claude_mpm/services/{ticket_manager_dependency_injection.py → ticket_manager_di.py} +12 -39
  58. claude_mpm/services/{legacy_ticketing_service.py → ticketing_service_original.py} +9 -16
  59. claude_mpm/services/version_control/semantic_versioning.py +9 -10
  60. claude_mpm/services/websocket_server.py +376 -0
  61. claude_mpm/utils/dependency_manager.py +211 -0
  62. claude_mpm/utils/import_migration_example.py +80 -0
  63. claude_mpm/utils/path_operations.py +0 -20
  64. claude_mpm/web/open_dashboard.py +34 -0
  65. {claude_mpm-3.1.3.dist-info → claude_mpm-3.3.0.dist-info}/METADATA +20 -9
  66. {claude_mpm-3.1.3.dist-info → claude_mpm-3.3.0.dist-info}/RECORD +71 -50
  67. claude_mpm-3.3.0.dist-info/entry_points.txt +7 -0
  68. claude_mpm/cli_old.py +0 -728
  69. claude_mpm/models/common.py +0 -41
  70. claude_mpm/models/lifecycle.py +0 -97
  71. claude_mpm/models/modification.py +0 -126
  72. claude_mpm/models/persistence.py +0 -57
  73. claude_mpm/models/registry.py +0 -91
  74. claude_mpm/security/__init__.py +0 -8
  75. claude_mpm/security/bash_validator.py +0 -393
  76. claude_mpm-3.1.3.dist-info/entry_points.txt +0 -4
  77. /claude_mpm/{cli_enhancements.py → experimental/cli_enhancements.py} +0 -0
  78. {claude_mpm-3.1.3.dist-info → claude_mpm-3.3.0.dist-info}/WHEEL +0 -0
  79. {claude_mpm-3.1.3.dist-info → claude_mpm-3.3.0.dist-info}/licenses/LICENSE +0 -0
  80. {claude_mpm-3.1.3.dist-info → claude_mpm-3.3.0.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
- """Simplified Claude runner replacing the complex orchestrator system."""
1
+ """Claude runner with both exec and subprocess launch methods."""
2
2
 
3
3
  import json
4
4
  import os
@@ -8,6 +8,7 @@ import time
8
8
  from datetime import datetime
9
9
  from pathlib import Path
10
10
  from typing import Optional
11
+ import uuid
11
12
 
12
13
  try:
13
14
  from claude_mpm.services.agent_deployment import AgentDeploymentService
@@ -19,28 +20,38 @@ except ImportError:
19
20
  from claude_mpm.core.logger import get_logger, get_project_logger, ProjectLogger
20
21
 
21
22
 
22
- class SimpleClaudeRunner:
23
+ class ClaudeRunner:
23
24
  """
24
- Simplified Claude runner that replaces the entire orchestrator system.
25
+ Claude runner that replaces the entire orchestrator system.
25
26
 
26
27
  This does exactly what we need:
27
28
  1. Deploy native agents to .claude/agents/
28
- 2. Run Claude CLI with basic subprocess calls
29
+ 2. Run Claude CLI with either exec or subprocess
29
30
  3. Extract tickets if needed
30
31
  4. Handle both interactive and non-interactive modes
32
+
33
+ Supports two launch methods:
34
+ - exec: Replace current process (default for backward compatibility)
35
+ - subprocess: Launch as child process for more control
31
36
  """
32
37
 
33
38
  def __init__(
34
39
  self,
35
40
  enable_tickets: bool = True,
36
41
  log_level: str = "OFF",
37
- claude_args: Optional[list] = None
42
+ claude_args: Optional[list] = None,
43
+ launch_method: str = "exec", # "exec" or "subprocess"
44
+ enable_websocket: bool = False,
45
+ websocket_port: int = 8765
38
46
  ):
39
- """Initialize the simple runner."""
47
+ """Initialize the Claude runner."""
40
48
  self.enable_tickets = enable_tickets
41
49
  self.log_level = log_level
42
- self.logger = get_logger("simple_runner")
50
+ self.logger = get_logger("claude_runner")
43
51
  self.claude_args = claude_args or []
52
+ self.launch_method = launch_method
53
+ self.enable_websocket = enable_websocket
54
+ self.websocket_port = websocket_port
44
55
 
45
56
  # Initialize project logger for session logging
46
57
  self.project_logger = None
@@ -48,7 +59,7 @@ class SimpleClaudeRunner:
48
59
  try:
49
60
  self.project_logger = get_project_logger(log_level)
50
61
  self.project_logger.log_system(
51
- "Initializing SimpleClaudeRunner",
62
+ f"Initializing ClaudeRunner with {launch_method} launcher",
52
63
  level="INFO",
53
64
  component="runner"
54
65
  )
@@ -76,12 +87,16 @@ class SimpleClaudeRunner:
76
87
  self.session_log_file = self.project_logger.session_dir / "system.jsonl"
77
88
  self._log_session_event({
78
89
  "event": "session_start",
79
- "runner": "SimpleClaudeRunner",
90
+ "runner": "ClaudeRunner",
80
91
  "enable_tickets": enable_tickets,
81
- "log_level": log_level
92
+ "log_level": log_level,
93
+ "launch_method": launch_method
82
94
  })
83
95
  except Exception as e:
84
96
  self.logger.debug(f"Failed to create session log file: {e}")
97
+
98
+ # Initialize WebSocket server reference
99
+ self.websocket_server = None
85
100
 
86
101
  def setup_agents(self) -> bool:
87
102
  """Deploy native agents to .claude/agents/."""
@@ -137,6 +152,28 @@ class SimpleClaudeRunner:
137
152
 
138
153
  def run_interactive(self, initial_context: Optional[str] = None):
139
154
  """Run Claude in interactive mode."""
155
+ # Start WebSocket server if enabled
156
+ if self.enable_websocket:
157
+ try:
158
+ # Lazy import to avoid circular dependencies
159
+ from claude_mpm.services.websocket_server import WebSocketServer
160
+ self.websocket_server = WebSocketServer(port=self.websocket_port)
161
+ self.websocket_server.start()
162
+
163
+ # Generate session ID
164
+ session_id = str(uuid.uuid4())
165
+ working_dir = os.getcwd()
166
+
167
+ # Notify session start
168
+ self.websocket_server.session_started(
169
+ session_id=session_id,
170
+ launch_method=self.launch_method,
171
+ working_dir=working_dir
172
+ )
173
+ except Exception as e:
174
+ self.logger.warning(f"Failed to start WebSocket server: {e}")
175
+ self.websocket_server = None
176
+
140
177
  # Get version
141
178
  try:
142
179
  from claude_mpm import __version__
@@ -195,31 +232,50 @@ class SimpleClaudeRunner:
195
232
  clean_env.pop(var, None)
196
233
 
197
234
  # Set the correct working directory for Claude Code
198
- # WHY: We're already in the user's directory thanks to __main__.py restoration
199
- # We just need to ensure CLAUDE_WORKSPACE is set correctly
200
- current_dir = os.getcwd()
201
- clean_env['CLAUDE_WORKSPACE'] = current_dir
202
- self.logger.info(f"Working directory: {current_dir}")
203
-
204
- # Log directory context for debugging
235
+ # If CLAUDE_MPM_USER_PWD is set, use that as the working directory
205
236
  if 'CLAUDE_MPM_USER_PWD' in clean_env:
206
- self.logger.debug(f"User PWD from env: {clean_env['CLAUDE_MPM_USER_PWD']}")
237
+ user_pwd = clean_env['CLAUDE_MPM_USER_PWD']
238
+ clean_env['CLAUDE_WORKSPACE'] = user_pwd
239
+ # Also change to that directory before launching Claude
240
+ try:
241
+ os.chdir(user_pwd)
242
+ self.logger.info(f"Changed working directory to: {user_pwd}")
243
+ except Exception as e:
244
+ self.logger.warning(f"Could not change to user directory {user_pwd}: {e}")
207
245
 
208
246
  print("Launching Claude...")
209
247
 
210
248
  if self.project_logger:
211
249
  self.project_logger.log_system(
212
- "Launching Claude interactive mode",
250
+ f"Launching Claude interactive mode with {self.launch_method}",
213
251
  level="INFO",
214
252
  component="session"
215
253
  )
216
254
  self._log_session_event({
217
255
  "event": "launching_claude_interactive",
218
- "command": " ".join(cmd)
256
+ "command": " ".join(cmd),
257
+ "method": self.launch_method
219
258
  })
220
259
 
221
- # Replace current process with Claude
222
- os.execvpe(cmd[0], cmd, clean_env)
260
+ # Notify WebSocket clients
261
+ if self.websocket_server:
262
+ self.websocket_server.claude_status_changed(
263
+ status="starting",
264
+ message="Launching Claude interactive session"
265
+ )
266
+
267
+ # Launch using selected method
268
+ if self.launch_method == "subprocess":
269
+ self._launch_subprocess_interactive(cmd, clean_env)
270
+ else:
271
+ # Default to exec for backward compatibility
272
+ if self.websocket_server:
273
+ # Notify before exec (we won't be able to after)
274
+ self.websocket_server.claude_status_changed(
275
+ status="running",
276
+ message="Claude process started (exec mode)"
277
+ )
278
+ os.execvpe(cmd[0], cmd, clean_env)
223
279
 
224
280
  except Exception as e:
225
281
  print(f"Failed to launch Claude: {e}")
@@ -234,6 +290,13 @@ class SimpleClaudeRunner:
234
290
  "error": str(e),
235
291
  "exception_type": type(e).__name__
236
292
  })
293
+
294
+ # Notify WebSocket clients of error
295
+ if self.websocket_server:
296
+ self.websocket_server.claude_status_changed(
297
+ status="error",
298
+ message=f"Failed to launch Claude: {e}"
299
+ )
237
300
  # Fallback to subprocess
238
301
  try:
239
302
  # Use the same clean_env we prepared earlier
@@ -266,6 +329,28 @@ class SimpleClaudeRunner:
266
329
  """Run Claude with a single prompt and return success status."""
267
330
  start_time = time.time()
268
331
 
332
+ # Start WebSocket server if enabled
333
+ if self.enable_websocket:
334
+ try:
335
+ # Lazy import to avoid circular dependencies
336
+ from claude_mpm.services.websocket_server import WebSocketServer
337
+ self.websocket_server = WebSocketServer(port=self.websocket_port)
338
+ self.websocket_server.start()
339
+
340
+ # Generate session ID
341
+ session_id = str(uuid.uuid4())
342
+ working_dir = os.getcwd()
343
+
344
+ # Notify session start
345
+ self.websocket_server.session_started(
346
+ session_id=session_id,
347
+ launch_method="oneshot",
348
+ working_dir=working_dir
349
+ )
350
+ except Exception as e:
351
+ self.logger.warning(f"Failed to start WebSocket server: {e}")
352
+ self.websocket_server = None
353
+
269
354
  # Check for /mpm: commands
270
355
  if prompt.strip().startswith("/mpm:"):
271
356
  return self._handle_mpm_command(prompt.strip())
@@ -312,15 +397,19 @@ class SimpleClaudeRunner:
312
397
  env = os.environ.copy()
313
398
 
314
399
  # Set the correct working directory for Claude Code
315
- # WHY: We're already in the user's directory thanks to __main__.py restoration
316
- # We just need to ensure CLAUDE_WORKSPACE is set correctly
317
- current_dir = os.getcwd()
318
- env['CLAUDE_WORKSPACE'] = current_dir
319
- self.logger.info(f"Working directory for subprocess: {current_dir}")
320
-
321
- # Log directory context for debugging
322
400
  if 'CLAUDE_MPM_USER_PWD' in env:
323
- self.logger.debug(f"User PWD from env: {env['CLAUDE_MPM_USER_PWD']}")
401
+ user_pwd = env['CLAUDE_MPM_USER_PWD']
402
+ env['CLAUDE_WORKSPACE'] = user_pwd
403
+ # Change to that directory before running Claude
404
+ try:
405
+ original_cwd = os.getcwd()
406
+ os.chdir(user_pwd)
407
+ self.logger.info(f"Changed working directory to: {user_pwd}")
408
+ except Exception as e:
409
+ self.logger.warning(f"Could not change to user directory {user_pwd}: {e}")
410
+ original_cwd = None
411
+ else:
412
+ original_cwd = None
324
413
 
325
414
  # Run Claude
326
415
  if self.project_logger:
@@ -330,15 +419,31 @@ class SimpleClaudeRunner:
330
419
  component="session"
331
420
  )
332
421
 
422
+ # Notify WebSocket clients
423
+ if self.websocket_server:
424
+ self.websocket_server.claude_status_changed(
425
+ status="running",
426
+ message="Executing Claude oneshot command"
427
+ )
428
+
333
429
  result = subprocess.run(cmd, capture_output=True, text=True, env=env)
334
430
 
335
- # No need to restore directory since we didn't change it
431
+ # Restore original directory if we changed it
432
+ if original_cwd:
433
+ try:
434
+ os.chdir(original_cwd)
435
+ except Exception:
436
+ pass
336
437
  execution_time = time.time() - start_time
337
438
 
338
439
  if result.returncode == 0:
339
440
  response = result.stdout.strip()
340
441
  print(response)
341
442
 
443
+ # Broadcast output to WebSocket clients
444
+ if self.websocket_server and response:
445
+ self.websocket_server.claude_output(response, "stdout")
446
+
342
447
  if self.project_logger:
343
448
  # Log successful completion
344
449
  self.project_logger.log_system(
@@ -368,6 +473,17 @@ class SimpleClaudeRunner:
368
473
  "indicators": [p for p in ["Task(", "subagent_type=", "engineer agent", "qa agent"]
369
474
  if p.lower() in response.lower()]
370
475
  })
476
+
477
+ # Notify WebSocket clients about delegation
478
+ if self.websocket_server:
479
+ # Try to extract agent name
480
+ agent_name = self._extract_agent_from_response(response)
481
+ if agent_name:
482
+ self.websocket_server.agent_delegated(
483
+ agent=agent_name,
484
+ task=prompt[:100],
485
+ status="detected"
486
+ )
371
487
 
372
488
  # Extract tickets if enabled
373
489
  if self.enable_tickets and self.ticket_manager and response:
@@ -378,6 +494,14 @@ class SimpleClaudeRunner:
378
494
  error_msg = result.stderr or "Unknown error"
379
495
  print(f"Error: {error_msg}")
380
496
 
497
+ # Broadcast error to WebSocket clients
498
+ if self.websocket_server:
499
+ self.websocket_server.claude_output(error_msg, "stderr")
500
+ self.websocket_server.claude_status_changed(
501
+ status="error",
502
+ message=f"Command failed with code {result.returncode}"
503
+ )
504
+
381
505
  if self.project_logger:
382
506
  self.project_logger.log_system(
383
507
  f"Non-interactive session failed: {error_msg}",
@@ -423,6 +547,14 @@ class SimpleClaudeRunner:
423
547
  )
424
548
  except Exception as e:
425
549
  self.logger.debug(f"Failed to log session summary: {e}")
550
+
551
+ # End WebSocket session
552
+ if self.websocket_server:
553
+ self.websocket_server.claude_status_changed(
554
+ status="stopped",
555
+ message="Session completed"
556
+ )
557
+ self.websocket_server.session_ended()
426
558
 
427
559
  def _extract_tickets(self, text: str):
428
560
  """Extract tickets from Claude's response."""
@@ -509,6 +641,28 @@ class SimpleClaudeRunner:
509
641
  text_lower = text.lower()
510
642
  return any(pattern.lower() in text_lower for pattern in delegation_patterns)
511
643
 
644
+ def _extract_agent_from_response(self, text: str) -> Optional[str]:
645
+ """Try to extract agent name from delegation response."""
646
+ # Look for common patterns
647
+ import re
648
+
649
+ # Pattern 1: subagent_type="agent_name"
650
+ match = re.search(r'subagent_type=["\']([^"\']*)["\'\)]', text)
651
+ if match:
652
+ return match.group(1)
653
+
654
+ # Pattern 2: "engineer agent" etc
655
+ agent_names = [
656
+ "engineer", "qa", "documentation", "research",
657
+ "security", "ops", "version_control", "data_engineer"
658
+ ]
659
+ text_lower = text.lower()
660
+ for agent in agent_names:
661
+ if f"{agent} agent" in text_lower or f"agent: {agent}" in text_lower:
662
+ return agent
663
+
664
+ return None
665
+
512
666
  def _handle_mpm_command(self, prompt: str) -> bool:
513
667
  """Handle /mpm: commands directly without going to Claude."""
514
668
  try:
@@ -584,6 +738,138 @@ class SimpleClaudeRunner:
584
738
  f.write(json.dumps(log_entry) + '\n')
585
739
  except Exception as e:
586
740
  self.logger.debug(f"Failed to log session event: {e}")
741
+
742
+ def _launch_subprocess_interactive(self, cmd: list, env: dict):
743
+ """Launch Claude as a subprocess with PTY for interactive mode."""
744
+ import pty
745
+ import select
746
+ import termios
747
+ import tty
748
+ import signal
749
+
750
+ # Save original terminal settings
751
+ original_tty = None
752
+ if sys.stdin.isatty():
753
+ original_tty = termios.tcgetattr(sys.stdin)
754
+
755
+ # Create PTY
756
+ master_fd, slave_fd = pty.openpty()
757
+
758
+ try:
759
+ # Start Claude process
760
+ process = subprocess.Popen(
761
+ cmd,
762
+ stdin=slave_fd,
763
+ stdout=slave_fd,
764
+ stderr=slave_fd,
765
+ env=env
766
+ )
767
+
768
+ # Close slave in parent
769
+ os.close(slave_fd)
770
+
771
+ if self.project_logger:
772
+ self.project_logger.log_system(
773
+ f"Claude subprocess started with PID {process.pid}",
774
+ level="INFO",
775
+ component="subprocess"
776
+ )
777
+
778
+ # Notify WebSocket clients
779
+ if self.websocket_server:
780
+ self.websocket_server.claude_status_changed(
781
+ status="running",
782
+ pid=process.pid,
783
+ message="Claude subprocess started"
784
+ )
785
+
786
+ # Set terminal to raw mode for proper interaction
787
+ if sys.stdin.isatty():
788
+ tty.setraw(sys.stdin)
789
+
790
+ # Handle Ctrl+C gracefully
791
+ def signal_handler(signum, frame):
792
+ if process.poll() is None:
793
+ process.terminate()
794
+ raise KeyboardInterrupt()
795
+
796
+ signal.signal(signal.SIGINT, signal_handler)
797
+
798
+ # I/O loop
799
+ while True:
800
+ # Check if process is still running
801
+ if process.poll() is not None:
802
+ break
803
+
804
+ # Check for data from Claude or stdin
805
+ r, _, _ = select.select([master_fd, sys.stdin], [], [], 0)
806
+
807
+ if master_fd in r:
808
+ try:
809
+ data = os.read(master_fd, 4096)
810
+ if data:
811
+ os.write(sys.stdout.fileno(), data)
812
+ # Broadcast output to WebSocket clients
813
+ if self.websocket_server:
814
+ try:
815
+ # Decode and send
816
+ output = data.decode('utf-8', errors='replace')
817
+ self.websocket_server.claude_output(output, "stdout")
818
+ except Exception as e:
819
+ self.logger.debug(f"Failed to broadcast output: {e}")
820
+ else:
821
+ break # EOF
822
+ except OSError:
823
+ break
824
+
825
+ if sys.stdin in r:
826
+ try:
827
+ data = os.read(sys.stdin.fileno(), 4096)
828
+ if data:
829
+ os.write(master_fd, data)
830
+ except OSError:
831
+ break
832
+
833
+ # Wait for process to complete
834
+ process.wait()
835
+
836
+ if self.project_logger:
837
+ self.project_logger.log_system(
838
+ f"Claude subprocess exited with code {process.returncode}",
839
+ level="INFO",
840
+ component="subprocess"
841
+ )
842
+
843
+ # Notify WebSocket clients
844
+ if self.websocket_server:
845
+ self.websocket_server.claude_status_changed(
846
+ status="stopped",
847
+ message=f"Claude subprocess exited with code {process.returncode}"
848
+ )
849
+
850
+ finally:
851
+ # Restore terminal
852
+ if original_tty and sys.stdin.isatty():
853
+ termios.tcsetattr(sys.stdin, termios.TCSADRAIN, original_tty)
854
+
855
+ # Close PTY
856
+ try:
857
+ os.close(master_fd)
858
+ except:
859
+ pass
860
+
861
+ # Ensure process is terminated
862
+ if 'process' in locals() and process.poll() is None:
863
+ process.terminate()
864
+ try:
865
+ process.wait(timeout=2)
866
+ except subprocess.TimeoutExpired:
867
+ process.kill()
868
+ process.wait()
869
+
870
+ # End WebSocket session if in subprocess mode
871
+ if self.websocket_server:
872
+ self.websocket_server.session_ended()
587
873
 
588
874
 
589
875
  def create_simple_context() -> str:
@@ -612,10 +898,14 @@ automatically normalize them to lowercase-hyphenated format for the Task tool.
612
898
  Work efficiently and delegate appropriately to subagents when needed."""
613
899
 
614
900
 
901
+ # Backward compatibility alias
902
+ SimpleClaudeRunner = ClaudeRunner
903
+
904
+
615
905
  # Convenience functions for backward compatibility
616
906
  def run_claude_interactive(context: Optional[str] = None):
617
907
  """Run Claude interactively with optional context."""
618
- runner = SimpleClaudeRunner()
908
+ runner = ClaudeRunner()
619
909
  if context is None:
620
910
  context = create_simple_context()
621
911
  runner.run_interactive(context)
@@ -623,7 +913,7 @@ def run_claude_interactive(context: Optional[str] = None):
623
913
 
624
914
  def run_claude_oneshot(prompt: str, context: Optional[str] = None) -> bool:
625
915
  """Run Claude with a single prompt."""
626
- runner = SimpleClaudeRunner()
916
+ runner = ClaudeRunner()
627
917
  if context is None:
628
918
  context = create_simple_context()
629
919
  return runner.run_oneshot(prompt, context)