claude-mpm 5.6.17__py3-none-any.whl → 5.6.33__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.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/VERSION +1 -1
- claude_mpm/cli/commands/commander.py +7 -7
- claude_mpm/cli/parsers/commander_parser.py +2 -2
- claude_mpm/cli/startup.py +36 -19
- claude_mpm/commander/chat/cli.py +38 -3
- claude_mpm/commander/config.py +5 -3
- claude_mpm/commander/daemon.py +9 -0
- claude_mpm/commander/frameworks/base.py +4 -1
- claude_mpm/commander/instance_manager.py +124 -11
- claude_mpm/core/claude_runner.py +22 -13
- claude_mpm/core/config.py +3 -3
- claude_mpm/core/config_constants.py +74 -9
- claude_mpm/core/constants.py +56 -12
- claude_mpm/core/interactive_session.py +5 -4
- claude_mpm/core/logging_utils.py +4 -2
- claude_mpm/core/network_config.py +148 -0
- claude_mpm/core/oneshot_session.py +7 -6
- claude_mpm/core/output_style_manager.py +5 -2
- claude_mpm/core/socketio_pool.py +13 -5
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +1 -1
- claude_mpm/hooks/claude_hooks/event_handlers.py +262 -89
- claude_mpm/hooks/claude_hooks/hook_handler.py +81 -32
- claude_mpm/hooks/claude_hooks/installer.py +90 -28
- claude_mpm/hooks/claude_hooks/memory_integration.py +1 -1
- claude_mpm/hooks/claude_hooks/response_tracking.py +1 -1
- claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/container.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/protocols.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +2 -2
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +2 -2
- claude_mpm/hooks/claude_hooks/services/container.py +310 -0
- claude_mpm/hooks/claude_hooks/services/protocols.py +328 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +2 -2
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +2 -2
- claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
- claude_mpm/hooks/templates/pre_tool_use_template.py +6 -6
- claude_mpm/scripts/claude-hook-handler.sh +3 -3
- claude_mpm/scripts/start_activity_logging.py +0 -0
- claude_mpm/services/command_deployment_service.py +44 -26
- claude_mpm/services/hook_installer_service.py +77 -8
- claude_mpm/services/pm_skills_deployer.py +3 -2
- {claude_mpm-5.6.17.dist-info → claude_mpm-5.6.33.dist-info}/METADATA +1 -1
- {claude_mpm-5.6.17.dist-info → claude_mpm-5.6.33.dist-info}/RECORD +56 -78
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-314.pyc +0 -0
- {claude_mpm-5.6.17.dist-info → claude_mpm-5.6.33.dist-info}/WHEEL +0 -0
- {claude_mpm-5.6.17.dist-info → claude_mpm-5.6.33.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.6.17.dist-info → claude_mpm-5.6.33.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.6.17.dist-info → claude_mpm-5.6.33.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.6.17.dist-info → claude_mpm-5.6.33.dist-info}/top_level.txt +0 -0
claude_mpm/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.6.
|
|
1
|
+
5.6.33
|
|
@@ -43,12 +43,12 @@ def display_commander_banner():
|
|
|
43
43
|
|
|
44
44
|
# Commander ASCII art banner
|
|
45
45
|
banner = f"""
|
|
46
|
-
{CYAN}╭{
|
|
47
|
-
{CYAN}│{RESET}{BOLD} ⚡ MPM Commander {RESET}{DIM}v{version}{RESET}{
|
|
48
|
-
{CYAN}│{RESET}{DIM} Multi-Project AI Orchestration{RESET}{
|
|
49
|
-
{CYAN}├{
|
|
50
|
-
{CYAN}│{RESET} {YELLOW}ALPHA{RESET} - APIs may change {
|
|
51
|
-
{CYAN}╰{
|
|
46
|
+
{CYAN}╭{"─" * (width - 2)}╮{RESET}
|
|
47
|
+
{CYAN}│{RESET}{BOLD} ⚡ MPM Commander {RESET}{DIM}v{version}{RESET}{" " * (width - 24 - len(version))}│
|
|
48
|
+
{CYAN}│{RESET}{DIM} Multi-Project AI Orchestration{RESET}{" " * (width - 36)}│
|
|
49
|
+
{CYAN}├{"─" * (width - 2)}┤{RESET}
|
|
50
|
+
{CYAN}│{RESET} {YELLOW}ALPHA{RESET} - APIs may change {" " * (width - 55)}│
|
|
51
|
+
{CYAN}╰{"─" * (width - 2)}╯{RESET}
|
|
52
52
|
"""
|
|
53
53
|
print(banner)
|
|
54
54
|
|
|
@@ -142,7 +142,7 @@ def handle_commander_command(args) -> int:
|
|
|
142
142
|
print() # Blank line after loading
|
|
143
143
|
|
|
144
144
|
# Get arguments
|
|
145
|
-
port = getattr(args, "port",
|
|
145
|
+
port = getattr(args, "port", 8766) # NetworkPorts.COMMANDER_DEFAULT
|
|
146
146
|
host = getattr(args, "host", "127.0.0.1")
|
|
147
147
|
state_dir = getattr(args, "state_dir", None)
|
|
148
148
|
no_chat = getattr(args, "no_chat", False) or getattr(args, "daemon_only", False)
|
|
@@ -77,8 +77,8 @@ Examples:
|
|
|
77
77
|
commander_parser.add_argument(
|
|
78
78
|
"--port",
|
|
79
79
|
type=int,
|
|
80
|
-
default=
|
|
81
|
-
help="Port for internal services (default:
|
|
80
|
+
default=8766, # NetworkPorts.COMMANDER_DEFAULT
|
|
81
|
+
help="Port for internal services (default: 8766)",
|
|
82
82
|
)
|
|
83
83
|
|
|
84
84
|
# Optional: State directory
|
claude_mpm/cli/startup.py
CHANGED
|
@@ -34,13 +34,13 @@ def sync_hooks_on_startup(quiet: bool = False) -> bool:
|
|
|
34
34
|
installer = HookInstaller()
|
|
35
35
|
|
|
36
36
|
# Show brief status (hooks sync is fast)
|
|
37
|
-
if not quiet:
|
|
37
|
+
if not quiet and sys.stdout.isatty():
|
|
38
38
|
print("Syncing Claude Code hooks...", end=" ", flush=True)
|
|
39
39
|
|
|
40
40
|
# Reinstall hooks (force=True ensures update)
|
|
41
41
|
success = installer.install_hooks(force=True)
|
|
42
42
|
|
|
43
|
-
if not quiet:
|
|
43
|
+
if not quiet and sys.stdout.isatty():
|
|
44
44
|
if success:
|
|
45
45
|
print("✓")
|
|
46
46
|
else:
|
|
@@ -49,7 +49,7 @@ def sync_hooks_on_startup(quiet: bool = False) -> bool:
|
|
|
49
49
|
return success
|
|
50
50
|
|
|
51
51
|
except Exception as e:
|
|
52
|
-
if not quiet:
|
|
52
|
+
if not quiet and sys.stdout.isatty():
|
|
53
53
|
print("(error)")
|
|
54
54
|
# Log but don't fail startup
|
|
55
55
|
from ..core.logger import get_logger
|
|
@@ -280,11 +280,13 @@ def deploy_bundled_skills():
|
|
|
280
280
|
if deployment_result.get("deployed"):
|
|
281
281
|
# Show simple feedback for deployed skills
|
|
282
282
|
deployed_count = len(deployment_result["deployed"])
|
|
283
|
-
|
|
283
|
+
if sys.stdout.isatty():
|
|
284
|
+
print(f"✓ Bundled skills ready ({deployed_count} deployed)", flush=True)
|
|
284
285
|
logger.info(f"Skills: Deployed {deployed_count} skill(s)")
|
|
285
286
|
elif not deployment_result.get("errors"):
|
|
286
287
|
# No deployment needed, skills already present
|
|
287
|
-
|
|
288
|
+
if sys.stdout.isatty():
|
|
289
|
+
print("✓ Bundled skills ready", flush=True)
|
|
288
290
|
|
|
289
291
|
if deployment_result.get("errors"):
|
|
290
292
|
logger.warning(
|
|
@@ -318,7 +320,8 @@ def discover_and_link_runtime_skills():
|
|
|
318
320
|
|
|
319
321
|
discover_skills()
|
|
320
322
|
# Show simple success feedback
|
|
321
|
-
|
|
323
|
+
if sys.stdout.isatty():
|
|
324
|
+
print("✓ Runtime skills linked", flush=True)
|
|
322
325
|
except Exception as e:
|
|
323
326
|
# Import logger here to avoid circular imports
|
|
324
327
|
from ..core.logger import get_logger
|
|
@@ -373,7 +376,8 @@ def deploy_output_style_on_startup():
|
|
|
373
376
|
|
|
374
377
|
if all_up_to_date:
|
|
375
378
|
# Show feedback that output styles are ready
|
|
376
|
-
|
|
379
|
+
if sys.stdout.isatty():
|
|
380
|
+
print("✓ Output styles ready", flush=True)
|
|
377
381
|
return
|
|
378
382
|
|
|
379
383
|
# Deploy all styles using the manager
|
|
@@ -383,7 +387,8 @@ def deploy_output_style_on_startup():
|
|
|
383
387
|
deployed_count = sum(1 for success in results.values() if success)
|
|
384
388
|
|
|
385
389
|
if deployed_count > 0:
|
|
386
|
-
|
|
390
|
+
if sys.stdout.isatty():
|
|
391
|
+
print(f"✓ Output styles deployed ({deployed_count} styles)", flush=True)
|
|
387
392
|
else:
|
|
388
393
|
# Deployment failed - log but don't fail startup
|
|
389
394
|
from ..core.logger import get_logger
|
|
@@ -1290,9 +1295,11 @@ def verify_and_show_pm_skills():
|
|
|
1290
1295
|
if result.verified:
|
|
1291
1296
|
# Show verified status with count
|
|
1292
1297
|
total_required = len(REQUIRED_PM_SKILLS)
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1298
|
+
if sys.stdout.isatty():
|
|
1299
|
+
print(
|
|
1300
|
+
f"✓ PM skills: {total_required}/{total_required} verified",
|
|
1301
|
+
flush=True,
|
|
1302
|
+
)
|
|
1296
1303
|
else:
|
|
1297
1304
|
# Show warning with details
|
|
1298
1305
|
missing_count = len(result.missing_skills)
|
|
@@ -1311,13 +1318,15 @@ def verify_and_show_pm_skills():
|
|
|
1311
1318
|
if "Auto-repaired" in result.message:
|
|
1312
1319
|
# Auto-repair succeeded
|
|
1313
1320
|
total_required = len(REQUIRED_PM_SKILLS)
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1321
|
+
if sys.stdout.isatty():
|
|
1322
|
+
print(
|
|
1323
|
+
f"✓ PM skills: {total_required}/{total_required} verified (auto-repaired)",
|
|
1324
|
+
flush=True,
|
|
1325
|
+
)
|
|
1318
1326
|
else:
|
|
1319
1327
|
# Auto-repair failed or not attempted
|
|
1320
|
-
|
|
1328
|
+
if sys.stdout.isatty():
|
|
1329
|
+
print(f"⚠ PM skills: {status}", flush=True)
|
|
1321
1330
|
|
|
1322
1331
|
# Log warnings for debugging
|
|
1323
1332
|
from ..core.logger import get_logger
|
|
@@ -1516,7 +1525,9 @@ def check_mcp_auto_configuration():
|
|
|
1516
1525
|
from ..services.mcp_gateway.auto_configure import check_and_configure_mcp
|
|
1517
1526
|
|
|
1518
1527
|
# Show progress feedback - this operation can take 10+ seconds
|
|
1519
|
-
|
|
1528
|
+
# Only show progress message in TTY mode to avoid interfering with Claude Code's status display
|
|
1529
|
+
if sys.stdout.isatty():
|
|
1530
|
+
print("Checking MCP configuration...", end="", flush=True)
|
|
1520
1531
|
|
|
1521
1532
|
# This function handles all the logic:
|
|
1522
1533
|
# - Checks if already configured
|
|
@@ -1527,11 +1538,17 @@ def check_mcp_auto_configuration():
|
|
|
1527
1538
|
check_and_configure_mcp()
|
|
1528
1539
|
|
|
1529
1540
|
# Clear the "Checking..." message by overwriting with spaces
|
|
1530
|
-
|
|
1541
|
+
# Only use carriage return clearing if stdout is a real TTY
|
|
1542
|
+
if sys.stdout.isatty():
|
|
1543
|
+
print("\r" + " " * 30 + "\r", end="", flush=True)
|
|
1544
|
+
# In non-TTY mode, don't print anything - the "Checking..." message will just remain on its line
|
|
1531
1545
|
|
|
1532
1546
|
except Exception as e:
|
|
1533
1547
|
# Clear progress message on error
|
|
1534
|
-
|
|
1548
|
+
# Only use carriage return clearing if stdout is a real TTY
|
|
1549
|
+
if sys.stdout.isatty():
|
|
1550
|
+
print("\r" + " " * 30 + "\r", end="", flush=True)
|
|
1551
|
+
# In non-TTY mode, don't print anything - the "Checking..." message will just remain on its line
|
|
1535
1552
|
|
|
1536
1553
|
# Non-critical - log but don't fail
|
|
1537
1554
|
from ..core.logger import get_logger
|
claude_mpm/commander/chat/cli.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
+
from dataclasses import dataclass
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import Optional
|
|
7
8
|
|
|
@@ -26,20 +27,47 @@ load_env()
|
|
|
26
27
|
logger = logging.getLogger(__name__)
|
|
27
28
|
|
|
28
29
|
|
|
30
|
+
@dataclass
|
|
31
|
+
class CommanderCLIConfig:
|
|
32
|
+
"""Configuration for Commander CLI mode.
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
summarize_responses: Whether to use LLM to summarize instance responses
|
|
36
|
+
port: Port for internal services (reserved for future use)
|
|
37
|
+
state_dir: Directory for state persistence (optional)
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
>>> config = CommanderCLIConfig(summarize_responses=False)
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
summarize_responses: bool = True
|
|
44
|
+
port: int = 8765
|
|
45
|
+
state_dir: Optional[Path] = None
|
|
46
|
+
|
|
47
|
+
|
|
29
48
|
async def run_commander(
|
|
30
49
|
port: int = 8765,
|
|
31
50
|
state_dir: Optional[Path] = None,
|
|
51
|
+
config: Optional[CommanderCLIConfig] = None,
|
|
32
52
|
) -> None:
|
|
33
53
|
"""Run Commander in interactive mode.
|
|
34
54
|
|
|
35
55
|
Args:
|
|
36
56
|
port: Port for internal services (unused currently).
|
|
37
57
|
state_dir: Directory for state persistence (optional).
|
|
58
|
+
config: Commander CLI configuration (optional, uses defaults if None).
|
|
38
59
|
|
|
39
60
|
Example:
|
|
40
61
|
>>> asyncio.run(run_commander())
|
|
41
62
|
# Starts interactive Commander REPL
|
|
63
|
+
>>> config = CommanderCLIConfig(summarize_responses=False)
|
|
64
|
+
>>> asyncio.run(run_commander(config=config))
|
|
65
|
+
# Starts Commander without response summarization
|
|
42
66
|
"""
|
|
67
|
+
# Use default config if not provided
|
|
68
|
+
if config is None:
|
|
69
|
+
config = CommanderCLIConfig(port=port, state_dir=state_dir)
|
|
70
|
+
|
|
43
71
|
# Setup logging
|
|
44
72
|
logging.basicConfig(
|
|
45
73
|
level=logging.INFO,
|
|
@@ -61,8 +89,8 @@ async def run_commander(
|
|
|
61
89
|
# Try to initialize LLM client (optional)
|
|
62
90
|
llm_client: Optional[OpenRouterClient] = None
|
|
63
91
|
try:
|
|
64
|
-
|
|
65
|
-
llm_client = OpenRouterClient(
|
|
92
|
+
llm_config = OpenRouterConfig()
|
|
93
|
+
llm_client = OpenRouterClient(llm_config)
|
|
66
94
|
logger.info("LLM client initialized")
|
|
67
95
|
except ValueError as e:
|
|
68
96
|
logger.warning(f"LLM client not available: {e}")
|
|
@@ -72,7 +100,14 @@ async def run_commander(
|
|
|
72
100
|
output_relay: Optional[OutputRelay] = None
|
|
73
101
|
if llm_client:
|
|
74
102
|
try:
|
|
75
|
-
summarizer
|
|
103
|
+
# Only create summarizer if summarize_responses is enabled
|
|
104
|
+
summarizer = None
|
|
105
|
+
if config.summarize_responses:
|
|
106
|
+
summarizer = OutputSummarizer(llm_client)
|
|
107
|
+
logger.info("Response summarization enabled")
|
|
108
|
+
else:
|
|
109
|
+
logger.info("Response summarization disabled")
|
|
110
|
+
|
|
76
111
|
handler = OutputHandler(orchestrator, summarizer)
|
|
77
112
|
formatter = OutputFormatter()
|
|
78
113
|
output_relay = OutputRelay(handler, formatter)
|
claude_mpm/commander/config.py
CHANGED
|
@@ -14,28 +14,30 @@ class DaemonConfig:
|
|
|
14
14
|
|
|
15
15
|
Attributes:
|
|
16
16
|
host: API server bind address
|
|
17
|
-
port: API server port
|
|
17
|
+
port: API server port (default: 8766 from NetworkPorts.COMMANDER_DEFAULT)
|
|
18
18
|
log_level: Logging level (DEBUG, INFO, WARNING, ERROR)
|
|
19
19
|
state_dir: Directory for state persistence
|
|
20
20
|
max_projects: Maximum concurrent projects
|
|
21
21
|
healthcheck_interval: Healthcheck interval in seconds
|
|
22
22
|
save_interval: State persistence interval in seconds
|
|
23
23
|
poll_interval: Event polling interval in seconds
|
|
24
|
+
summarize_responses: Whether to use LLM to summarize instance responses
|
|
24
25
|
|
|
25
26
|
Example:
|
|
26
|
-
>>> config = DaemonConfig(port=
|
|
27
|
+
>>> config = DaemonConfig(port=8766, log_level="DEBUG")
|
|
27
28
|
>>> config.state_dir
|
|
28
29
|
PosixPath('/Users/user/.claude-mpm/commander')
|
|
29
30
|
"""
|
|
30
31
|
|
|
31
32
|
host: str = "127.0.0.1"
|
|
32
|
-
port: int =
|
|
33
|
+
port: int = 8766 # Default commander port (from network_config.NetworkPorts.COMMANDER_DEFAULT)
|
|
33
34
|
log_level: str = "INFO"
|
|
34
35
|
state_dir: Path = Path.home() / ".claude-mpm" / "commander"
|
|
35
36
|
max_projects: int = 10
|
|
36
37
|
healthcheck_interval: int = 30
|
|
37
38
|
save_interval: int = 30
|
|
38
39
|
poll_interval: float = 2.0
|
|
40
|
+
summarize_responses: bool = True
|
|
39
41
|
|
|
40
42
|
def __post_init__(self):
|
|
41
43
|
"""Ensure state_dir is a Path object and create if needed."""
|
claude_mpm/commander/daemon.py
CHANGED
|
@@ -307,7 +307,16 @@ class CommanderDaemon:
|
|
|
307
307
|
|
|
308
308
|
Registers handlers for SIGINT and SIGTERM that trigger
|
|
309
309
|
daemon shutdown via asyncio event loop.
|
|
310
|
+
|
|
311
|
+
Note: Signal handlers can only be registered from the main thread.
|
|
312
|
+
If called from a background thread, registration is skipped.
|
|
310
313
|
"""
|
|
314
|
+
import threading
|
|
315
|
+
|
|
316
|
+
# Signal handlers can only be registered from the main thread
|
|
317
|
+
if threading.current_thread() is not threading.main_thread():
|
|
318
|
+
logger.info("Running in background thread - signal handlers skipped")
|
|
319
|
+
return
|
|
311
320
|
|
|
312
321
|
def handle_signal(signum: int, frame) -> None:
|
|
313
322
|
"""Handle shutdown signal.
|
|
@@ -19,6 +19,7 @@ class InstanceInfo:
|
|
|
19
19
|
pane_target: Tmux pane target (e.g., "%1")
|
|
20
20
|
git_branch: Current git branch if project is a git repo
|
|
21
21
|
git_status: Git status summary if project is a git repo
|
|
22
|
+
connected: Whether instance has an active adapter connection
|
|
22
23
|
|
|
23
24
|
Example:
|
|
24
25
|
>>> info = InstanceInfo(
|
|
@@ -28,7 +29,8 @@ class InstanceInfo:
|
|
|
28
29
|
... tmux_session="mpm-commander",
|
|
29
30
|
... pane_target="%1",
|
|
30
31
|
... git_branch="main",
|
|
31
|
-
... git_status="clean"
|
|
32
|
+
... git_status="clean",
|
|
33
|
+
... connected=True
|
|
32
34
|
... )
|
|
33
35
|
"""
|
|
34
36
|
|
|
@@ -39,6 +41,7 @@ class InstanceInfo:
|
|
|
39
41
|
pane_target: str
|
|
40
42
|
git_branch: Optional[str] = None
|
|
41
43
|
git_status: Optional[str] = None
|
|
44
|
+
connected: bool = False
|
|
42
45
|
|
|
43
46
|
|
|
44
47
|
class BaseFramework(ABC):
|
|
@@ -167,6 +167,20 @@ class InstanceManager:
|
|
|
167
167
|
startup_cmd = framework_obj.get_startup_command(project_path)
|
|
168
168
|
self.orchestrator.send_keys(pane_target, startup_cmd)
|
|
169
169
|
|
|
170
|
+
# Create communication adapter for the instance (only for Claude Code for now)
|
|
171
|
+
# Do this BEFORE creating InstanceInfo so we can set connected=True
|
|
172
|
+
has_adapter = False
|
|
173
|
+
if framework == "cc":
|
|
174
|
+
runtime_adapter = ClaudeCodeAdapter()
|
|
175
|
+
comm_adapter = ClaudeCodeCommunicationAdapter(
|
|
176
|
+
orchestrator=self.orchestrator,
|
|
177
|
+
pane_target=pane_target,
|
|
178
|
+
runtime_adapter=runtime_adapter,
|
|
179
|
+
)
|
|
180
|
+
self._adapters[name] = comm_adapter
|
|
181
|
+
has_adapter = True
|
|
182
|
+
logger.debug(f"Created communication adapter for instance '{name}'")
|
|
183
|
+
|
|
170
184
|
# Create instance info
|
|
171
185
|
instance = InstanceInfo(
|
|
172
186
|
name=name,
|
|
@@ -176,22 +190,12 @@ class InstanceManager:
|
|
|
176
190
|
pane_target=pane_target,
|
|
177
191
|
git_branch=git_branch,
|
|
178
192
|
git_status=git_status,
|
|
193
|
+
connected=has_adapter,
|
|
179
194
|
)
|
|
180
195
|
|
|
181
196
|
# Track instance
|
|
182
197
|
self._instances[name] = instance
|
|
183
198
|
|
|
184
|
-
# Create communication adapter for the instance (only for Claude Code for now)
|
|
185
|
-
if framework == "cc":
|
|
186
|
-
runtime_adapter = ClaudeCodeAdapter()
|
|
187
|
-
comm_adapter = ClaudeCodeCommunicationAdapter(
|
|
188
|
-
orchestrator=self.orchestrator,
|
|
189
|
-
pane_target=pane_target,
|
|
190
|
-
runtime_adapter=runtime_adapter,
|
|
191
|
-
)
|
|
192
|
-
self._adapters[name] = comm_adapter
|
|
193
|
-
logger.debug(f"Created communication adapter for instance '{name}'")
|
|
194
|
-
|
|
195
199
|
logger.info(
|
|
196
200
|
f"Started instance '{name}' with framework '{framework}' at {project_path}"
|
|
197
201
|
)
|
|
@@ -226,6 +230,7 @@ class InstanceManager:
|
|
|
226
230
|
# Remove adapter if exists
|
|
227
231
|
if name in self._adapters:
|
|
228
232
|
del self._adapters[name]
|
|
233
|
+
instance.connected = False
|
|
229
234
|
logger.debug(f"Removed adapter for instance '{name}'")
|
|
230
235
|
|
|
231
236
|
# Remove from tracking
|
|
@@ -335,3 +340,111 @@ class InstanceManager:
|
|
|
335
340
|
... print(chunk, end='')
|
|
336
341
|
"""
|
|
337
342
|
return self._adapters.get(name)
|
|
343
|
+
|
|
344
|
+
async def rename_instance(self, old_name: str, new_name: str) -> bool:
|
|
345
|
+
"""Rename an instance.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
old_name: Current instance name
|
|
349
|
+
new_name: New instance name
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
True if renamed successfully
|
|
353
|
+
|
|
354
|
+
Raises:
|
|
355
|
+
InstanceNotFoundError: If old_name doesn't exist
|
|
356
|
+
InstanceAlreadyExistsError: If new_name already exists
|
|
357
|
+
|
|
358
|
+
Example:
|
|
359
|
+
>>> manager = InstanceManager(orchestrator)
|
|
360
|
+
>>> await manager.rename_instance("myapp", "myapp-v2")
|
|
361
|
+
True
|
|
362
|
+
"""
|
|
363
|
+
# Validate old_name exists
|
|
364
|
+
if old_name not in self._instances:
|
|
365
|
+
raise InstanceNotFoundError(old_name)
|
|
366
|
+
|
|
367
|
+
# Validate new_name doesn't exist
|
|
368
|
+
if new_name in self._instances:
|
|
369
|
+
raise InstanceAlreadyExistsError(new_name)
|
|
370
|
+
|
|
371
|
+
# Get instance and update name
|
|
372
|
+
instance = self._instances[old_name]
|
|
373
|
+
instance.name = new_name
|
|
374
|
+
|
|
375
|
+
# Update _instances dict (remove old key, add new)
|
|
376
|
+
del self._instances[old_name]
|
|
377
|
+
self._instances[new_name] = instance
|
|
378
|
+
|
|
379
|
+
# Update _adapters dict if exists
|
|
380
|
+
if old_name in self._adapters:
|
|
381
|
+
adapter = self._adapters[old_name]
|
|
382
|
+
del self._adapters[old_name]
|
|
383
|
+
self._adapters[new_name] = adapter
|
|
384
|
+
logger.debug(f"Moved adapter from '{old_name}' to '{new_name}'")
|
|
385
|
+
|
|
386
|
+
logger.info(f"Renamed instance from '{old_name}' to '{new_name}'")
|
|
387
|
+
|
|
388
|
+
return True
|
|
389
|
+
|
|
390
|
+
async def close_instance(self, name: str) -> bool:
|
|
391
|
+
"""Close and remove an instance.
|
|
392
|
+
|
|
393
|
+
Alias for stop_instance that provides clearer semantics for closing.
|
|
394
|
+
|
|
395
|
+
Args:
|
|
396
|
+
name: Instance name to close
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
True if closed successfully
|
|
400
|
+
|
|
401
|
+
Raises:
|
|
402
|
+
InstanceNotFoundError: If instance not found
|
|
403
|
+
|
|
404
|
+
Example:
|
|
405
|
+
>>> manager = InstanceManager(orchestrator)
|
|
406
|
+
>>> await manager.close_instance("myapp")
|
|
407
|
+
True
|
|
408
|
+
"""
|
|
409
|
+
return await self.stop_instance(name)
|
|
410
|
+
|
|
411
|
+
async def disconnect_instance(self, name: str) -> bool:
|
|
412
|
+
"""Disconnect from an instance without closing it.
|
|
413
|
+
|
|
414
|
+
The instance keeps running but we stop communication.
|
|
415
|
+
Removes the adapter while keeping the instance tracked.
|
|
416
|
+
|
|
417
|
+
Args:
|
|
418
|
+
name: Instance name to disconnect from
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
True if disconnected successfully
|
|
422
|
+
|
|
423
|
+
Raises:
|
|
424
|
+
InstanceNotFoundError: If instance not found
|
|
425
|
+
|
|
426
|
+
Example:
|
|
427
|
+
>>> manager = InstanceManager(orchestrator)
|
|
428
|
+
>>> await manager.disconnect_instance("myapp")
|
|
429
|
+
True
|
|
430
|
+
>>> # Instance still running, but no adapter connection
|
|
431
|
+
>>> adapter = manager.get_adapter("myapp")
|
|
432
|
+
>>> print(adapter)
|
|
433
|
+
None
|
|
434
|
+
"""
|
|
435
|
+
# Validate instance exists
|
|
436
|
+
if name not in self._instances:
|
|
437
|
+
raise InstanceNotFoundError(name)
|
|
438
|
+
|
|
439
|
+
instance = self._instances[name]
|
|
440
|
+
|
|
441
|
+
# Remove adapter if exists (but keep instance)
|
|
442
|
+
if name in self._adapters:
|
|
443
|
+
# Could add cleanup here if adapter has resources to close
|
|
444
|
+
del self._adapters[name]
|
|
445
|
+
instance.connected = False
|
|
446
|
+
logger.info(f"Disconnected from instance '{name}' (instance still running)")
|
|
447
|
+
else:
|
|
448
|
+
logger.debug(f"No adapter to disconnect for instance '{name}'")
|
|
449
|
+
|
|
450
|
+
return True
|
claude_mpm/core/claude_runner.py
CHANGED
|
@@ -214,8 +214,12 @@ class ClaudeRunner:
|
|
|
214
214
|
)
|
|
215
215
|
|
|
216
216
|
def _get_deployment_state_path(self) -> Path:
|
|
217
|
-
"""Get path to deployment state file.
|
|
218
|
-
|
|
217
|
+
"""Get path to deployment state file.
|
|
218
|
+
|
|
219
|
+
CRITICAL: Must match path used by startup.py::_save_deployment_state_after_reconciliation()
|
|
220
|
+
Located at: .claude-mpm/cache/deployment_state.json
|
|
221
|
+
"""
|
|
222
|
+
return Path.cwd() / ".claude-mpm" / "cache" / "deployment_state.json"
|
|
219
223
|
|
|
220
224
|
def _calculate_deployment_hash(self, agents_dir: Path) -> str:
|
|
221
225
|
"""Calculate hash of all agent files for change detection.
|
|
@@ -331,18 +335,23 @@ class ClaudeRunner:
|
|
|
331
335
|
def setup_agents(self) -> bool:
|
|
332
336
|
"""Deploy native agents to .claude/agents/."""
|
|
333
337
|
try:
|
|
334
|
-
#
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
if
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
component="deployment",
|
|
338
|
+
# SIMPLE CHECK: If agents already exist from reconciliation, skip deployment
|
|
339
|
+
# This ensures reconciliation's user-configured agents are never overwritten
|
|
340
|
+
agents_dir = Path.cwd() / ".claude" / "agents"
|
|
341
|
+
if agents_dir.exists():
|
|
342
|
+
existing_agents = list(agents_dir.glob("*.md"))
|
|
343
|
+
if len(existing_agents) > 0:
|
|
344
|
+
# Reconciliation already deployed agents - skip
|
|
345
|
+
self.logger.debug(
|
|
346
|
+
f"Skipping setup_agents: {len(existing_agents)} agents already deployed by reconciliation"
|
|
344
347
|
)
|
|
345
|
-
|
|
348
|
+
if self.project_logger:
|
|
349
|
+
self.project_logger.log_system(
|
|
350
|
+
f"Agents already deployed via reconciliation: {len(existing_agents)} agents",
|
|
351
|
+
level="DEBUG",
|
|
352
|
+
component="deployment",
|
|
353
|
+
)
|
|
354
|
+
return True
|
|
346
355
|
|
|
347
356
|
if self.project_logger:
|
|
348
357
|
self.project_logger.log_system(
|
claude_mpm/core/config.py
CHANGED
|
@@ -499,7 +499,7 @@ class Config:
|
|
|
499
499
|
# Socket.IO server health and recovery configuration
|
|
500
500
|
"socketio_server": {
|
|
501
501
|
"host": "localhost",
|
|
502
|
-
"port":
|
|
502
|
+
"port": 8768, # Default SocketIO port (from network_config.NetworkPorts)
|
|
503
503
|
"enable_health_monitoring": True,
|
|
504
504
|
"enable_recovery": True,
|
|
505
505
|
"health_monitoring": {
|
|
@@ -540,7 +540,7 @@ class Config:
|
|
|
540
540
|
# Monitor server configuration (decoupled from dashboard)
|
|
541
541
|
"monitor_server": {
|
|
542
542
|
"host": "localhost",
|
|
543
|
-
"port": 8765, # Default monitor port (
|
|
543
|
+
"port": 8765, # Default monitor port (from network_config.NetworkPorts.MONITOR_DEFAULT)
|
|
544
544
|
"enable_health_monitoring": True,
|
|
545
545
|
"auto_start": False, # Don't auto-start with dashboard by default
|
|
546
546
|
"event_buffer_size": 2000, # Larger buffer for monitor server
|
|
@@ -549,7 +549,7 @@ class Config:
|
|
|
549
549
|
# Dashboard server configuration (connects to monitor)
|
|
550
550
|
"dashboard_server": {
|
|
551
551
|
"host": "localhost",
|
|
552
|
-
"port":
|
|
552
|
+
"port": 8767, # Dashboard UI port (from network_config.NetworkPorts.DASHBOARD_DEFAULT)
|
|
553
553
|
"monitor_host": "localhost", # Monitor server host to connect to
|
|
554
554
|
"monitor_port": 8765, # Monitor server port to connect to
|
|
555
555
|
"auto_connect_monitor": True, # Automatically connect to monitor
|