claude-mpm 3.3.0__py3-none-any.whl → 3.4.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/agents/templates/data_engineer.json +1 -1
- claude_mpm/agents/templates/documentation.json +1 -1
- claude_mpm/agents/templates/engineer.json +1 -1
- claude_mpm/agents/templates/ops.json +1 -1
- claude_mpm/agents/templates/pm.json +1 -1
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/research.json +1 -1
- claude_mpm/agents/templates/security.json +1 -1
- claude_mpm/agents/templates/test_integration.json +112 -0
- claude_mpm/agents/templates/version_control.json +1 -1
- claude_mpm/cli/commands/memory.py +749 -26
- claude_mpm/cli/commands/run.py +115 -14
- claude_mpm/cli/parser.py +89 -1
- claude_mpm/constants.py +6 -0
- claude_mpm/core/claude_runner.py +74 -11
- claude_mpm/core/config.py +1 -1
- claude_mpm/core/session_manager.py +46 -0
- claude_mpm/core/simple_runner.py +74 -11
- claude_mpm/hooks/builtin/mpm_command_hook.py +5 -5
- claude_mpm/hooks/claude_hooks/hook_handler.py +213 -30
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +9 -2
- claude_mpm/hooks/memory_integration_hook.py +51 -5
- claude_mpm/services/__init__.py +23 -5
- claude_mpm/services/agent_memory_manager.py +800 -71
- claude_mpm/services/memory_builder.py +823 -0
- claude_mpm/services/memory_optimizer.py +619 -0
- claude_mpm/services/memory_router.py +445 -0
- claude_mpm/services/project_analyzer.py +771 -0
- claude_mpm/services/socketio_server.py +649 -45
- claude_mpm/services/version_control/git_operations.py +26 -0
- claude_mpm-3.4.0.dist-info/METADATA +183 -0
- {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/RECORD +36 -52
- claude_mpm/agents/agent-template.yaml +0 -83
- claude_mpm/agents/templates/test-integration-agent.md +0 -34
- claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +0 -6
- claude_mpm/cli/README.md +0 -109
- claude_mpm/cli_module/refactoring_guide.md +0 -253
- claude_mpm/core/agent_registry.py.bak +0 -312
- claude_mpm/core/base_service.py.bak +0 -406
- claude_mpm/core/websocket_handler.py +0 -233
- claude_mpm/hooks/README.md +0 -97
- claude_mpm/orchestration/SUBPROCESS_DESIGN.md +0 -66
- claude_mpm/schemas/README_SECURITY.md +0 -92
- claude_mpm/schemas/agent_schema.json +0 -395
- claude_mpm/schemas/agent_schema_documentation.md +0 -181
- claude_mpm/schemas/agent_schema_security_notes.md +0 -165
- claude_mpm/schemas/examples/standard_workflow.json +0 -505
- claude_mpm/schemas/ticket_workflow_documentation.md +0 -482
- claude_mpm/schemas/ticket_workflow_schema.json +0 -590
- claude_mpm/services/framework_claude_md_generator/README.md +0 -92
- claude_mpm/services/parent_directory_manager/README.md +0 -83
- claude_mpm/services/version_control/VERSION +0 -1
- claude_mpm/services/websocket_server.py +0 -376
- claude_mpm-3.3.0.dist-info/METADATA +0 -432
- {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/WHEEL +0 -0
- {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/top_level.txt +0 -0
claude_mpm/cli/commands/run.py
CHANGED
|
@@ -11,12 +11,13 @@ import sys
|
|
|
11
11
|
import time
|
|
12
12
|
import webbrowser
|
|
13
13
|
from pathlib import Path
|
|
14
|
+
from datetime import datetime
|
|
14
15
|
|
|
15
16
|
from ...core.logger import get_logger
|
|
16
17
|
from ...constants import LogLevel
|
|
17
18
|
from ..utils import get_user_input, list_agent_versions_at_startup
|
|
18
19
|
from ...utils.dependency_manager import ensure_socketio_dependencies
|
|
19
|
-
from ...deployment_paths import
|
|
20
|
+
from ...deployment_paths import get_scripts_dir, get_package_root
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def filter_claude_mpm_args(claude_args):
|
|
@@ -80,10 +81,19 @@ def filter_claude_mpm_args(claude_args):
|
|
|
80
81
|
# Also skip the next argument if this flag expects a value
|
|
81
82
|
value_expecting_flags = {
|
|
82
83
|
'--websocket-port', '--launch-method', '--logging', '--log-dir',
|
|
83
|
-
'--framework-path', '--agents-dir', '-i', '--input'
|
|
84
|
+
'--framework-path', '--agents-dir', '-i', '--input'
|
|
84
85
|
}
|
|
86
|
+
optional_value_flags = {
|
|
87
|
+
'--resume' # These flags can have optional values (nargs="?")
|
|
88
|
+
}
|
|
89
|
+
|
|
85
90
|
if arg in value_expecting_flags and i < len(claude_args):
|
|
86
91
|
i += 1 # Skip the value too
|
|
92
|
+
elif arg in optional_value_flags and i < len(claude_args):
|
|
93
|
+
# For optional value flags, only skip next arg if it doesn't start with --
|
|
94
|
+
next_arg = claude_args[i]
|
|
95
|
+
if not next_arg.startswith('--'):
|
|
96
|
+
i += 1 # Skip the value
|
|
87
97
|
else:
|
|
88
98
|
# This is not a claude-mpm flag, keep it
|
|
89
99
|
filtered_args.append(arg)
|
|
@@ -92,6 +102,58 @@ def filter_claude_mpm_args(claude_args):
|
|
|
92
102
|
return filtered_args
|
|
93
103
|
|
|
94
104
|
|
|
105
|
+
def create_session_context(session_id, session_manager):
|
|
106
|
+
"""
|
|
107
|
+
Create enhanced context for resumed sessions.
|
|
108
|
+
|
|
109
|
+
WHY: When resuming a session, we want to provide Claude with context about
|
|
110
|
+
the previous session including what agents were used and when it was created.
|
|
111
|
+
This helps maintain continuity across session boundaries.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
session_id: Session ID being resumed
|
|
115
|
+
session_manager: SessionManager instance
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Enhanced context string with session information
|
|
119
|
+
"""
|
|
120
|
+
try:
|
|
121
|
+
from ...core.claude_runner import create_simple_context
|
|
122
|
+
except ImportError:
|
|
123
|
+
from claude_mpm.core.claude_runner import create_simple_context
|
|
124
|
+
|
|
125
|
+
base_context = create_simple_context()
|
|
126
|
+
|
|
127
|
+
session_data = session_manager.get_session_by_id(session_id)
|
|
128
|
+
if not session_data:
|
|
129
|
+
return base_context
|
|
130
|
+
|
|
131
|
+
# Add session resumption information
|
|
132
|
+
session_info = f"""
|
|
133
|
+
|
|
134
|
+
# Session Resumption
|
|
135
|
+
|
|
136
|
+
You are resuming session {session_id[:8]}... which was:
|
|
137
|
+
- Created: {session_data.get('created_at', 'unknown')}
|
|
138
|
+
- Last used: {session_data.get('last_used', 'unknown')}
|
|
139
|
+
- Context: {session_data.get('context', 'default')}
|
|
140
|
+
- Use count: {session_data.get('use_count', 0)}
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
# Add information about agents previously run in this session
|
|
144
|
+
agents_run = session_data.get('agents_run', [])
|
|
145
|
+
if agents_run:
|
|
146
|
+
session_info += "\n- Previous agent activity:\n"
|
|
147
|
+
for agent_info in agents_run[-5:]: # Show last 5 agents
|
|
148
|
+
session_info += f" • {agent_info.get('agent', 'unknown')}: {agent_info.get('task', 'no description')[:50]}...\n"
|
|
149
|
+
if len(agents_run) > 5:
|
|
150
|
+
session_info += f" (and {len(agents_run) - 5} other agent interactions)\n"
|
|
151
|
+
|
|
152
|
+
session_info += "\nContinue from where you left off in this session."
|
|
153
|
+
|
|
154
|
+
return base_context + session_info
|
|
155
|
+
|
|
156
|
+
|
|
95
157
|
def run_session(args):
|
|
96
158
|
"""
|
|
97
159
|
Run a simplified Claude session.
|
|
@@ -112,8 +174,44 @@ def run_session(args):
|
|
|
112
174
|
|
|
113
175
|
try:
|
|
114
176
|
from ...core.claude_runner import ClaudeRunner, create_simple_context
|
|
177
|
+
from ...core.session_manager import SessionManager
|
|
115
178
|
except ImportError:
|
|
116
179
|
from claude_mpm.core.claude_runner import ClaudeRunner, create_simple_context
|
|
180
|
+
from claude_mpm.core.session_manager import SessionManager
|
|
181
|
+
|
|
182
|
+
# Handle session resumption
|
|
183
|
+
session_manager = SessionManager()
|
|
184
|
+
resume_session_id = None
|
|
185
|
+
resume_context = None
|
|
186
|
+
|
|
187
|
+
if hasattr(args, 'resume') and args.resume:
|
|
188
|
+
if args.resume == "last":
|
|
189
|
+
# Resume the last interactive session
|
|
190
|
+
resume_session_id = session_manager.get_last_interactive_session()
|
|
191
|
+
if resume_session_id:
|
|
192
|
+
session_data = session_manager.get_session_by_id(resume_session_id)
|
|
193
|
+
if session_data:
|
|
194
|
+
resume_context = session_data.get("context", "default")
|
|
195
|
+
logger.info(f"Resuming session {resume_session_id} (context: {resume_context})")
|
|
196
|
+
print(f"🔄 Resuming session {resume_session_id[:8]}... (created: {session_data.get('created_at', 'unknown')})")
|
|
197
|
+
else:
|
|
198
|
+
logger.warning(f"Session {resume_session_id} not found")
|
|
199
|
+
else:
|
|
200
|
+
logger.info("No recent interactive sessions found")
|
|
201
|
+
print("ℹ️ No recent interactive sessions found to resume")
|
|
202
|
+
else:
|
|
203
|
+
# Resume specific session by ID
|
|
204
|
+
resume_session_id = args.resume
|
|
205
|
+
session_data = session_manager.get_session_by_id(resume_session_id)
|
|
206
|
+
if session_data:
|
|
207
|
+
resume_context = session_data.get("context", "default")
|
|
208
|
+
logger.info(f"Resuming session {resume_session_id} (context: {resume_context})")
|
|
209
|
+
print(f"🔄 Resuming session {resume_session_id[:8]}... (context: {resume_context})")
|
|
210
|
+
else:
|
|
211
|
+
logger.error(f"Session {resume_session_id} not found")
|
|
212
|
+
print(f"❌ Session {resume_session_id} not found")
|
|
213
|
+
print("💡 Use 'claude-mpm sessions' to list available sessions")
|
|
214
|
+
return
|
|
117
215
|
|
|
118
216
|
# Skip native agents if disabled
|
|
119
217
|
if getattr(args, 'no_native_agents', False):
|
|
@@ -189,8 +287,19 @@ def run_session(args):
|
|
|
189
287
|
# Pass information about whether we already opened the browser in run.py
|
|
190
288
|
runner._browser_opened_by_cli = getattr(args, '_browser_opened_by_cli', False)
|
|
191
289
|
|
|
192
|
-
# Create
|
|
193
|
-
|
|
290
|
+
# Create context - use resumed session context if available
|
|
291
|
+
if resume_session_id and resume_context:
|
|
292
|
+
# For resumed sessions, create enhanced context with session information
|
|
293
|
+
context = create_session_context(resume_session_id, session_manager)
|
|
294
|
+
# Update session usage
|
|
295
|
+
session_manager.active_sessions[resume_session_id]["last_used"] = datetime.now().isoformat()
|
|
296
|
+
session_manager.active_sessions[resume_session_id]["use_count"] += 1
|
|
297
|
+
session_manager._save_sessions()
|
|
298
|
+
else:
|
|
299
|
+
# Create a new session for tracking
|
|
300
|
+
new_session_id = session_manager.create_session("default")
|
|
301
|
+
context = create_simple_context()
|
|
302
|
+
logger.info(f"Created new session {new_session_id}")
|
|
194
303
|
|
|
195
304
|
# For monitor mode, we handled everything in launch_socketio_monitor
|
|
196
305
|
# No need for ClaudeRunner browser delegation
|
|
@@ -260,16 +369,8 @@ def launch_socketio_monitor(port, logger):
|
|
|
260
369
|
|
|
261
370
|
socketio_port = port
|
|
262
371
|
|
|
263
|
-
#
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
if not html_file_path.exists():
|
|
267
|
-
logger.error(f"Monitor HTML file not found: {html_file_path}")
|
|
268
|
-
print(f"❌ Monitor HTML file not found: {html_file_path}")
|
|
269
|
-
return False, False
|
|
270
|
-
|
|
271
|
-
# Create file:// URL with port parameter
|
|
272
|
-
dashboard_url = f'file://{html_file_path.absolute()}?port={socketio_port}'
|
|
372
|
+
# Use HTTP URL to access dashboard from Socket.IO server
|
|
373
|
+
dashboard_url = f'http://localhost:{socketio_port}'
|
|
273
374
|
|
|
274
375
|
# Check if Socket.IO server is already running
|
|
275
376
|
server_running = _check_socketio_server_running(socketio_port, logger)
|
claude_mpm/cli/parser.py
CHANGED
|
@@ -119,6 +119,13 @@ def add_run_arguments(parser: argparse.ArgumentParser) -> None:
|
|
|
119
119
|
default=8765,
|
|
120
120
|
help="WebSocket server port (default: 8765)"
|
|
121
121
|
)
|
|
122
|
+
run_group.add_argument(
|
|
123
|
+
"--resume",
|
|
124
|
+
type=str,
|
|
125
|
+
nargs="?",
|
|
126
|
+
const="last",
|
|
127
|
+
help="Resume a session (last session if no ID specified, or specific session ID)"
|
|
128
|
+
)
|
|
122
129
|
|
|
123
130
|
# Input/output options
|
|
124
131
|
io_group = parser.add_argument_group('input/output options')
|
|
@@ -214,6 +221,13 @@ def create_parser(prog_name: str = "claude-mpm", version: str = "0.0.0") -> argp
|
|
|
214
221
|
default=8765,
|
|
215
222
|
help="WebSocket server port (default: 8765)"
|
|
216
223
|
)
|
|
224
|
+
run_group.add_argument(
|
|
225
|
+
"--resume",
|
|
226
|
+
type=str,
|
|
227
|
+
nargs="?",
|
|
228
|
+
const="last",
|
|
229
|
+
help="Resume a session (last session if no ID specified, or specific session ID)"
|
|
230
|
+
)
|
|
217
231
|
|
|
218
232
|
# Input/output options
|
|
219
233
|
io_group = parser.add_argument_group('input/output options (when no command specified)')
|
|
@@ -352,6 +366,12 @@ def create_parser(prog_name: str = "claude-mpm", version: str = "0.0.0") -> argp
|
|
|
352
366
|
metavar="SUBCOMMAND"
|
|
353
367
|
)
|
|
354
368
|
|
|
369
|
+
# Init command
|
|
370
|
+
init_parser = memory_subparsers.add_parser(
|
|
371
|
+
MemoryCommands.INIT.value,
|
|
372
|
+
help="Initialize project-specific memories via PM agent"
|
|
373
|
+
)
|
|
374
|
+
|
|
355
375
|
# Status command
|
|
356
376
|
status_parser = memory_subparsers.add_parser(
|
|
357
377
|
MemoryCommands.STATUS.value,
|
|
@@ -365,7 +385,8 @@ def create_parser(prog_name: str = "claude-mpm", version: str = "0.0.0") -> argp
|
|
|
365
385
|
)
|
|
366
386
|
view_parser.add_argument(
|
|
367
387
|
"agent_id",
|
|
368
|
-
|
|
388
|
+
nargs="?",
|
|
389
|
+
help="Agent ID to view memory for (optional - shows all agents if not provided)"
|
|
369
390
|
)
|
|
370
391
|
|
|
371
392
|
# Add command
|
|
@@ -393,6 +414,73 @@ def create_parser(prog_name: str = "claude-mpm", version: str = "0.0.0") -> argp
|
|
|
393
414
|
help="Clean up old/unused memory files"
|
|
394
415
|
)
|
|
395
416
|
|
|
417
|
+
# Optimize command
|
|
418
|
+
optimize_parser = memory_subparsers.add_parser(
|
|
419
|
+
MemoryCommands.OPTIMIZE.value,
|
|
420
|
+
help="Optimize memory files by removing duplicates and consolidating similar items"
|
|
421
|
+
)
|
|
422
|
+
optimize_parser.add_argument(
|
|
423
|
+
"agent_id",
|
|
424
|
+
nargs="?",
|
|
425
|
+
help="Agent ID to optimize (optimize all if not specified)"
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
# Build command
|
|
429
|
+
build_parser = memory_subparsers.add_parser(
|
|
430
|
+
MemoryCommands.BUILD.value,
|
|
431
|
+
help="Build agent memories from project documentation"
|
|
432
|
+
)
|
|
433
|
+
build_parser.add_argument(
|
|
434
|
+
"--force-rebuild",
|
|
435
|
+
action="store_true",
|
|
436
|
+
help="Force rebuild even if docs haven't changed"
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
# Cross-reference command
|
|
440
|
+
cross_ref_parser = memory_subparsers.add_parser(
|
|
441
|
+
MemoryCommands.CROSS_REF.value,
|
|
442
|
+
help="Find cross-references and common patterns across agent memories"
|
|
443
|
+
)
|
|
444
|
+
cross_ref_parser.add_argument(
|
|
445
|
+
"--query",
|
|
446
|
+
type=str,
|
|
447
|
+
help="Optional search query to filter cross-references"
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
# Route command
|
|
451
|
+
route_parser = memory_subparsers.add_parser(
|
|
452
|
+
MemoryCommands.ROUTE.value,
|
|
453
|
+
help="Test memory command routing logic"
|
|
454
|
+
)
|
|
455
|
+
route_parser.add_argument(
|
|
456
|
+
"--content",
|
|
457
|
+
type=str,
|
|
458
|
+
required=True,
|
|
459
|
+
help="Content to analyze for routing"
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
# Show command
|
|
463
|
+
show_parser = memory_subparsers.add_parser(
|
|
464
|
+
MemoryCommands.SHOW.value,
|
|
465
|
+
help="Show agent memories in user-friendly format with cross-references"
|
|
466
|
+
)
|
|
467
|
+
show_parser.add_argument(
|
|
468
|
+
"agent_id",
|
|
469
|
+
nargs="?",
|
|
470
|
+
help="Agent ID to show memory for (show all if not specified)"
|
|
471
|
+
)
|
|
472
|
+
show_parser.add_argument(
|
|
473
|
+
"--format",
|
|
474
|
+
choices=["summary", "detailed", "full"],
|
|
475
|
+
default="summary",
|
|
476
|
+
help="Display format: summary (default), detailed, or full"
|
|
477
|
+
)
|
|
478
|
+
show_parser.add_argument(
|
|
479
|
+
"--raw",
|
|
480
|
+
action="store_true",
|
|
481
|
+
help="Output raw memory content in JSON format for programmatic processing"
|
|
482
|
+
)
|
|
483
|
+
|
|
396
484
|
return parser
|
|
397
485
|
|
|
398
486
|
|
claude_mpm/constants.py
CHANGED
|
@@ -58,10 +58,16 @@ class AgentCommands(str, Enum):
|
|
|
58
58
|
|
|
59
59
|
class MemoryCommands(str, Enum):
|
|
60
60
|
"""Memory subcommand constants."""
|
|
61
|
+
INIT = "init"
|
|
61
62
|
STATUS = "status"
|
|
62
63
|
VIEW = "view"
|
|
63
64
|
ADD = "add"
|
|
64
65
|
CLEAN = "clean"
|
|
66
|
+
OPTIMIZE = "optimize"
|
|
67
|
+
BUILD = "build"
|
|
68
|
+
CROSS_REF = "cross-ref"
|
|
69
|
+
ROUTE = "route"
|
|
70
|
+
SHOW = "show"
|
|
65
71
|
|
|
66
72
|
|
|
67
73
|
class CLIFlags(str, Enum):
|
claude_mpm/core/claude_runner.py
CHANGED
|
@@ -13,10 +13,14 @@ import uuid
|
|
|
13
13
|
try:
|
|
14
14
|
from claude_mpm.services.agent_deployment import AgentDeploymentService
|
|
15
15
|
from claude_mpm.services.ticket_manager import TicketManager
|
|
16
|
+
from claude_mpm.services.hook_service import HookService
|
|
17
|
+
from claude_mpm.core.config import Config
|
|
16
18
|
from claude_mpm.core.logger import get_logger, get_project_logger, ProjectLogger
|
|
17
19
|
except ImportError:
|
|
18
20
|
from claude_mpm.services.agent_deployment import AgentDeploymentService
|
|
19
21
|
from claude_mpm.services.ticket_manager import TicketManager
|
|
22
|
+
from claude_mpm.services.hook_service import HookService
|
|
23
|
+
from claude_mpm.core.config import Config
|
|
20
24
|
from claude_mpm.core.logger import get_logger, get_project_logger, ProjectLogger
|
|
21
25
|
|
|
22
26
|
|
|
@@ -76,6 +80,11 @@ class ClaudeRunner:
|
|
|
76
80
|
self.ticket_manager = None
|
|
77
81
|
self.enable_tickets = False
|
|
78
82
|
|
|
83
|
+
# Initialize hook service and register memory hooks
|
|
84
|
+
self.config = Config()
|
|
85
|
+
self.hook_service = HookService(self.config)
|
|
86
|
+
self._register_memory_hooks()
|
|
87
|
+
|
|
79
88
|
# Load system instructions
|
|
80
89
|
self.system_instructions = self._load_system_instructions()
|
|
81
90
|
|
|
@@ -95,7 +104,7 @@ class ClaudeRunner:
|
|
|
95
104
|
except Exception as e:
|
|
96
105
|
self.logger.debug(f"Failed to create session log file: {e}")
|
|
97
106
|
|
|
98
|
-
# Initialize
|
|
107
|
+
# Initialize Socket.IO server reference
|
|
99
108
|
self.websocket_server = None
|
|
100
109
|
|
|
101
110
|
def setup_agents(self) -> bool:
|
|
@@ -152,13 +161,14 @@ class ClaudeRunner:
|
|
|
152
161
|
|
|
153
162
|
def run_interactive(self, initial_context: Optional[str] = None):
|
|
154
163
|
"""Run Claude in interactive mode."""
|
|
155
|
-
#
|
|
164
|
+
# Connect to Socket.IO server if enabled
|
|
156
165
|
if self.enable_websocket:
|
|
157
166
|
try:
|
|
158
|
-
#
|
|
159
|
-
from claude_mpm.services.
|
|
160
|
-
self.websocket_server =
|
|
167
|
+
# Use Socket.IO client proxy to connect to monitoring server
|
|
168
|
+
from claude_mpm.services.socketio_server import SocketIOClientProxy
|
|
169
|
+
self.websocket_server = SocketIOClientProxy(port=self.websocket_port)
|
|
161
170
|
self.websocket_server.start()
|
|
171
|
+
self.logger.info("Connected to Socket.IO monitoring server")
|
|
162
172
|
|
|
163
173
|
# Generate session ID
|
|
164
174
|
session_id = str(uuid.uuid4())
|
|
@@ -171,7 +181,7 @@ class ClaudeRunner:
|
|
|
171
181
|
working_dir=working_dir
|
|
172
182
|
)
|
|
173
183
|
except Exception as e:
|
|
174
|
-
self.logger.warning(f"Failed to
|
|
184
|
+
self.logger.warning(f"Failed to connect to Socket.IO server: {e}")
|
|
175
185
|
self.websocket_server = None
|
|
176
186
|
|
|
177
187
|
# Get version
|
|
@@ -329,13 +339,14 @@ class ClaudeRunner:
|
|
|
329
339
|
"""Run Claude with a single prompt and return success status."""
|
|
330
340
|
start_time = time.time()
|
|
331
341
|
|
|
332
|
-
#
|
|
342
|
+
# Connect to Socket.IO server if enabled
|
|
333
343
|
if self.enable_websocket:
|
|
334
344
|
try:
|
|
335
|
-
#
|
|
336
|
-
from claude_mpm.services.
|
|
337
|
-
self.websocket_server =
|
|
345
|
+
# Use Socket.IO client proxy to connect to monitoring server
|
|
346
|
+
from claude_mpm.services.socketio_server import SocketIOClientProxy
|
|
347
|
+
self.websocket_server = SocketIOClientProxy(port=self.websocket_port)
|
|
338
348
|
self.websocket_server.start()
|
|
349
|
+
self.logger.info("Connected to Socket.IO monitoring server")
|
|
339
350
|
|
|
340
351
|
# Generate session ID
|
|
341
352
|
session_id = str(uuid.uuid4())
|
|
@@ -348,7 +359,7 @@ class ClaudeRunner:
|
|
|
348
359
|
working_dir=working_dir
|
|
349
360
|
)
|
|
350
361
|
except Exception as e:
|
|
351
|
-
self.logger.warning(f"Failed to
|
|
362
|
+
self.logger.warning(f"Failed to connect to Socket.IO server: {e}")
|
|
352
363
|
self.websocket_server = None
|
|
353
364
|
|
|
354
365
|
# Check for /mpm: commands
|
|
@@ -739,6 +750,58 @@ class ClaudeRunner:
|
|
|
739
750
|
except Exception as e:
|
|
740
751
|
self.logger.debug(f"Failed to log session event: {e}")
|
|
741
752
|
|
|
753
|
+
def _register_memory_hooks(self):
|
|
754
|
+
"""Register memory integration hooks with the hook service.
|
|
755
|
+
|
|
756
|
+
WHY: This activates the memory system by registering hooks that automatically
|
|
757
|
+
inject agent memory before delegation and extract learnings after delegation.
|
|
758
|
+
This is the critical connection point between the memory system and the CLI.
|
|
759
|
+
|
|
760
|
+
DESIGN DECISION: We register hooks here instead of in __init__ to ensure
|
|
761
|
+
all services are initialized first. Hooks are only registered if the memory
|
|
762
|
+
system is enabled in configuration.
|
|
763
|
+
"""
|
|
764
|
+
try:
|
|
765
|
+
# Only register if memory system is enabled
|
|
766
|
+
if not self.config.get('memory.enabled', True):
|
|
767
|
+
self.logger.debug("Memory system disabled - skipping hook registration")
|
|
768
|
+
return
|
|
769
|
+
|
|
770
|
+
# Import hook classes (lazy import to avoid circular dependencies)
|
|
771
|
+
from claude_mpm.hooks.memory_integration_hook import (
|
|
772
|
+
MemoryPreDelegationHook,
|
|
773
|
+
MemoryPostDelegationHook
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
# Register pre-delegation hook for memory injection
|
|
777
|
+
pre_hook = MemoryPreDelegationHook(self.config)
|
|
778
|
+
success = self.hook_service.register_hook(pre_hook)
|
|
779
|
+
if success:
|
|
780
|
+
self.logger.info(f"✅ Registered memory pre-delegation hook (priority: {pre_hook.priority})")
|
|
781
|
+
else:
|
|
782
|
+
self.logger.warning("❌ Failed to register memory pre-delegation hook")
|
|
783
|
+
|
|
784
|
+
# Register post-delegation hook if auto-learning is enabled
|
|
785
|
+
if self.config.get('memory.auto_learning', True): # Default to True now
|
|
786
|
+
post_hook = MemoryPostDelegationHook(self.config)
|
|
787
|
+
success = self.hook_service.register_hook(post_hook)
|
|
788
|
+
if success:
|
|
789
|
+
self.logger.info(f"✅ Registered memory post-delegation hook (priority: {post_hook.priority})")
|
|
790
|
+
else:
|
|
791
|
+
self.logger.warning("❌ Failed to register memory post-delegation hook")
|
|
792
|
+
else:
|
|
793
|
+
self.logger.info("ℹ️ Auto-learning disabled - skipping post-delegation hook")
|
|
794
|
+
|
|
795
|
+
# Log summary of registered hooks
|
|
796
|
+
hooks = self.hook_service.list_hooks()
|
|
797
|
+
pre_count = len(hooks.get('pre_delegation', []))
|
|
798
|
+
post_count = len(hooks.get('post_delegation', []))
|
|
799
|
+
self.logger.info(f"📋 Hook Service initialized: {pre_count} pre-delegation, {post_count} post-delegation hooks")
|
|
800
|
+
|
|
801
|
+
except Exception as e:
|
|
802
|
+
self.logger.error(f"❌ Failed to register memory hooks: {e}")
|
|
803
|
+
# Don't fail the entire initialization - memory system is optional
|
|
804
|
+
|
|
742
805
|
def _launch_subprocess_interactive(self, cmd: list, env: dict):
|
|
743
806
|
"""Launch Claude as a subprocess with PTY for interactive mode."""
|
|
744
807
|
import pty
|
claude_mpm/core/config.py
CHANGED
|
@@ -231,7 +231,7 @@ class Config:
|
|
|
231
231
|
# Agent Memory System configuration
|
|
232
232
|
"memory": {
|
|
233
233
|
"enabled": True, # Master switch for memory system
|
|
234
|
-
"auto_learning":
|
|
234
|
+
"auto_learning": True, # Automatic learning extraction (changed default to True)
|
|
235
235
|
"limits": {
|
|
236
236
|
"default_size_kb": 8, # Default file size limit
|
|
237
237
|
"max_sections": 10, # Maximum sections per file
|
|
@@ -117,6 +117,52 @@ class SessionManager:
|
|
|
117
117
|
if expired:
|
|
118
118
|
self._save_sessions()
|
|
119
119
|
|
|
120
|
+
def get_recent_sessions(self, limit: int = 10, context: Optional[str] = None) -> list:
|
|
121
|
+
"""Get recent sessions sorted by last used time.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
limit: Maximum number of sessions to return
|
|
125
|
+
context: Filter by context (optional)
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
List of session data dictionaries sorted by last_used descending
|
|
129
|
+
"""
|
|
130
|
+
sessions = list(self.active_sessions.values())
|
|
131
|
+
|
|
132
|
+
# Filter by context if specified
|
|
133
|
+
if context:
|
|
134
|
+
sessions = [s for s in sessions if s.get("context") == context]
|
|
135
|
+
|
|
136
|
+
# Sort by last_used descending (most recent first)
|
|
137
|
+
sessions.sort(key=lambda s: datetime.fromisoformat(s["last_used"]), reverse=True)
|
|
138
|
+
|
|
139
|
+
return sessions[:limit]
|
|
140
|
+
|
|
141
|
+
def get_session_by_id(self, session_id: str) -> Optional[Dict[str, Any]]:
|
|
142
|
+
"""Get session data by ID.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
session_id: Session ID to look up
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Session data dictionary or None if not found
|
|
149
|
+
"""
|
|
150
|
+
return self.active_sessions.get(session_id)
|
|
151
|
+
|
|
152
|
+
def get_last_interactive_session(self) -> Optional[str]:
|
|
153
|
+
"""Get the most recently used interactive session ID.
|
|
154
|
+
|
|
155
|
+
WHY: For --resume without arguments, we want to resume the last
|
|
156
|
+
interactive session (context="default" for regular Claude runs).
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Session ID of most recent interactive session, or None if none found
|
|
160
|
+
"""
|
|
161
|
+
recent_sessions = self.get_recent_sessions(limit=1, context="default")
|
|
162
|
+
if recent_sessions:
|
|
163
|
+
return recent_sessions[0]["id"]
|
|
164
|
+
return None
|
|
165
|
+
|
|
120
166
|
def _save_sessions(self):
|
|
121
167
|
"""Save sessions to disk."""
|
|
122
168
|
session_file = self.session_dir / "active_sessions.json"
|