claude-mpm 3.4.0__py3-none-any.whl → 3.4.2__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/cli/commands/memory.py +6 -1
- claude_mpm/core/config.py +160 -0
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +1 -1
- claude_mpm/scripts/socketio_daemon.py +49 -9
- claude_mpm/scripts/socketio_server_manager.py +370 -45
- claude_mpm/services/__init__.py +18 -0
- claude_mpm/services/agent_memory_manager.py +7 -5
- claude_mpm/services/exceptions.py +677 -0
- claude_mpm/services/health_monitor.py +892 -0
- claude_mpm/services/memory_builder.py +4 -2
- claude_mpm/services/memory_optimizer.py +6 -2
- claude_mpm/services/recovery_manager.py +670 -0
- claude_mpm/services/socketio_server.py +188 -11
- claude_mpm/services/standalone_socketio_server.py +703 -34
- {claude_mpm-3.4.0.dist-info → claude_mpm-3.4.2.dist-info}/METADATA +1 -1
- {claude_mpm-3.4.0.dist-info → claude_mpm-3.4.2.dist-info}/RECORD +21 -18
- /claude_mpm/{web → dashboard}/open_dashboard.py +0 -0
- {claude_mpm-3.4.0.dist-info → claude_mpm-3.4.2.dist-info}/WHEEL +0 -0
- {claude_mpm-3.4.0.dist-info → claude_mpm-3.4.2.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.4.0.dist-info → claude_mpm-3.4.2.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.4.0.dist-info → claude_mpm-3.4.2.dist-info}/top_level.txt +0 -0
|
@@ -294,7 +294,7 @@ class SocketIOServer:
|
|
|
294
294
|
self.app.router.add_get('/dashboard', self._handle_dashboard)
|
|
295
295
|
|
|
296
296
|
# Add static file serving for web assets
|
|
297
|
-
static_path = get_project_root() / 'src' / 'claude_mpm' / '
|
|
297
|
+
static_path = get_project_root() / 'src' / 'claude_mpm' / 'dashboard' / 'static'
|
|
298
298
|
if static_path.exists():
|
|
299
299
|
self.app.router.add_static('/static/', path=str(static_path), name='static')
|
|
300
300
|
|
|
@@ -348,7 +348,7 @@ class SocketIOServer:
|
|
|
348
348
|
|
|
349
349
|
async def _handle_dashboard(self, request):
|
|
350
350
|
"""Serve the dashboard HTML file."""
|
|
351
|
-
dashboard_path = get_project_root() / 'src' / 'claude_mpm' / '
|
|
351
|
+
dashboard_path = get_project_root() / 'src' / 'claude_mpm' / 'dashboard' / 'templates' / 'index.html'
|
|
352
352
|
self.logger.info(f"Dashboard requested, looking for: {dashboard_path}")
|
|
353
353
|
self.logger.info(f"Path exists: {dashboard_path.exists()}")
|
|
354
354
|
if dashboard_path.exists():
|
|
@@ -648,8 +648,13 @@ class SocketIOServer:
|
|
|
648
648
|
working_dir = file_dir
|
|
649
649
|
self.logger.info(f"No git repo found, using file's directory: {working_dir}")
|
|
650
650
|
|
|
651
|
-
|
|
651
|
+
# Handle case where working_dir is None, empty string, or 'Unknown'
|
|
652
|
+
original_working_dir = working_dir
|
|
653
|
+
if not working_dir or working_dir == 'Unknown' or working_dir.strip() == '':
|
|
652
654
|
working_dir = os.getcwd()
|
|
655
|
+
self.logger.info(f"[GIT-DIFF-DEBUG] working_dir was invalid ({repr(original_working_dir)}), using cwd: {working_dir}")
|
|
656
|
+
else:
|
|
657
|
+
self.logger.info(f"[GIT-DIFF-DEBUG] Using provided working_dir: {working_dir}")
|
|
653
658
|
|
|
654
659
|
# For read-only git operations, we can work from any directory
|
|
655
660
|
# by passing the -C flag to git commands instead of changing directories
|
|
@@ -999,32 +1004,47 @@ class SocketIOServer:
|
|
|
999
1004
|
try:
|
|
1000
1005
|
self.logger.info(f"[GIT-BRANCH-DEBUG] get_git_branch called with working_dir: {repr(working_dir)} (type: {type(working_dir)})")
|
|
1001
1006
|
|
|
1002
|
-
# Handle case where working_dir is None, empty string, or
|
|
1007
|
+
# Handle case where working_dir is None, empty string, or common invalid states
|
|
1003
1008
|
original_working_dir = working_dir
|
|
1004
|
-
|
|
1009
|
+
invalid_states = [
|
|
1010
|
+
None, '', 'Unknown', 'Loading...', 'Loading', 'undefined', 'null',
|
|
1011
|
+
'Not Connected', 'Invalid Directory', 'No Directory'
|
|
1012
|
+
]
|
|
1013
|
+
|
|
1014
|
+
if working_dir in invalid_states or (isinstance(working_dir, str) and working_dir.strip() == ''):
|
|
1005
1015
|
working_dir = os.getcwd()
|
|
1006
1016
|
self.logger.info(f"[GIT-BRANCH-DEBUG] working_dir was invalid ({repr(original_working_dir)}), using cwd: {working_dir}")
|
|
1007
1017
|
else:
|
|
1008
1018
|
self.logger.info(f"[GIT-BRANCH-DEBUG] Using provided working_dir: {working_dir}")
|
|
1019
|
+
|
|
1020
|
+
# Additional validation for obviously invalid paths
|
|
1021
|
+
if isinstance(working_dir, str):
|
|
1022
|
+
working_dir = working_dir.strip()
|
|
1023
|
+
# Check for null bytes or other invalid characters
|
|
1024
|
+
if '\x00' in working_dir:
|
|
1025
|
+
self.logger.warning(f"[GIT-BRANCH-DEBUG] working_dir contains null bytes, using cwd instead")
|
|
1026
|
+
working_dir = os.getcwd()
|
|
1009
1027
|
|
|
1010
1028
|
# Validate that the directory exists and is a valid path
|
|
1011
1029
|
if not os.path.exists(working_dir):
|
|
1012
|
-
self.logger.
|
|
1030
|
+
self.logger.info(f"[GIT-BRANCH-DEBUG] Directory does not exist: {working_dir} - responding gracefully")
|
|
1013
1031
|
await self.sio.emit('git_branch_response', {
|
|
1014
1032
|
'success': False,
|
|
1015
|
-
'error': f'Directory
|
|
1033
|
+
'error': f'Directory not found',
|
|
1016
1034
|
'working_dir': working_dir,
|
|
1017
|
-
'original_working_dir': original_working_dir
|
|
1035
|
+
'original_working_dir': original_working_dir,
|
|
1036
|
+
'detail': f'Path does not exist: {working_dir}'
|
|
1018
1037
|
}, room=sid)
|
|
1019
1038
|
return
|
|
1020
1039
|
|
|
1021
1040
|
if not os.path.isdir(working_dir):
|
|
1022
|
-
self.logger.
|
|
1041
|
+
self.logger.info(f"[GIT-BRANCH-DEBUG] Path is not a directory: {working_dir} - responding gracefully")
|
|
1023
1042
|
await self.sio.emit('git_branch_response', {
|
|
1024
1043
|
'success': False,
|
|
1025
|
-
'error': f'
|
|
1044
|
+
'error': f'Not a directory',
|
|
1026
1045
|
'working_dir': working_dir,
|
|
1027
|
-
'original_working_dir': original_working_dir
|
|
1046
|
+
'original_working_dir': original_working_dir,
|
|
1047
|
+
'detail': f'Path is not a directory: {working_dir}'
|
|
1028
1048
|
}, room=sid)
|
|
1029
1049
|
return
|
|
1030
1050
|
|
|
@@ -1140,6 +1160,163 @@ class SocketIOServer:
|
|
|
1140
1160
|
'file_path': data.get('file_path', 'unknown')
|
|
1141
1161
|
}, room=sid)
|
|
1142
1162
|
|
|
1163
|
+
@self.sio.event
|
|
1164
|
+
async def check_git_status(sid, data):
|
|
1165
|
+
"""Check git status for a file to determine if git diff icons should be shown"""
|
|
1166
|
+
import subprocess
|
|
1167
|
+
try:
|
|
1168
|
+
file_path = data.get('file_path')
|
|
1169
|
+
working_dir = data.get('working_dir', os.getcwd())
|
|
1170
|
+
|
|
1171
|
+
self.logger.info(f"[GIT-STATUS-DEBUG] check_git_status called with file_path: {repr(file_path)}, working_dir: {repr(working_dir)}")
|
|
1172
|
+
|
|
1173
|
+
if not file_path:
|
|
1174
|
+
await self.sio.emit('git_status_response', {
|
|
1175
|
+
'success': False,
|
|
1176
|
+
'error': 'file_path is required',
|
|
1177
|
+
'file_path': file_path
|
|
1178
|
+
}, room=sid)
|
|
1179
|
+
return
|
|
1180
|
+
|
|
1181
|
+
# Validate and sanitize working_dir
|
|
1182
|
+
original_working_dir = working_dir
|
|
1183
|
+
if not working_dir or working_dir == 'Unknown' or working_dir.strip() == '' or working_dir == '.':
|
|
1184
|
+
working_dir = os.getcwd()
|
|
1185
|
+
self.logger.info(f"[GIT-STATUS-DEBUG] working_dir was invalid ({repr(original_working_dir)}), using cwd: {working_dir}")
|
|
1186
|
+
else:
|
|
1187
|
+
self.logger.info(f"[GIT-STATUS-DEBUG] Using provided working_dir: {working_dir}")
|
|
1188
|
+
|
|
1189
|
+
# Check if the working directory exists and is a directory
|
|
1190
|
+
if not os.path.exists(working_dir):
|
|
1191
|
+
self.logger.warning(f"[GIT-STATUS-DEBUG] Directory does not exist: {working_dir}")
|
|
1192
|
+
await self.sio.emit('git_status_response', {
|
|
1193
|
+
'success': False,
|
|
1194
|
+
'error': f'Directory does not exist: {working_dir}',
|
|
1195
|
+
'file_path': file_path,
|
|
1196
|
+
'working_dir': working_dir,
|
|
1197
|
+
'original_working_dir': original_working_dir
|
|
1198
|
+
}, room=sid)
|
|
1199
|
+
return
|
|
1200
|
+
|
|
1201
|
+
if not os.path.isdir(working_dir):
|
|
1202
|
+
self.logger.warning(f"[GIT-STATUS-DEBUG] Path is not a directory: {working_dir}")
|
|
1203
|
+
await self.sio.emit('git_status_response', {
|
|
1204
|
+
'success': False,
|
|
1205
|
+
'error': f'Path is not a directory: {working_dir}',
|
|
1206
|
+
'file_path': file_path,
|
|
1207
|
+
'working_dir': working_dir,
|
|
1208
|
+
'original_working_dir': original_working_dir
|
|
1209
|
+
}, room=sid)
|
|
1210
|
+
return
|
|
1211
|
+
|
|
1212
|
+
# Check if this is a git repository
|
|
1213
|
+
self.logger.info(f"[GIT-STATUS-DEBUG] Checking if {working_dir} is a git repository")
|
|
1214
|
+
git_check = subprocess.run(
|
|
1215
|
+
["git", "-C", working_dir, "rev-parse", "--git-dir"],
|
|
1216
|
+
capture_output=True,
|
|
1217
|
+
text=True
|
|
1218
|
+
)
|
|
1219
|
+
|
|
1220
|
+
if git_check.returncode != 0:
|
|
1221
|
+
self.logger.info(f"[GIT-STATUS-DEBUG] Not a git repository: {working_dir}")
|
|
1222
|
+
await self.sio.emit('git_status_response', {
|
|
1223
|
+
'success': False,
|
|
1224
|
+
'error': 'Not a git repository',
|
|
1225
|
+
'file_path': file_path,
|
|
1226
|
+
'working_dir': working_dir,
|
|
1227
|
+
'original_working_dir': original_working_dir
|
|
1228
|
+
}, room=sid)
|
|
1229
|
+
return
|
|
1230
|
+
|
|
1231
|
+
# Determine if the file path should be made relative to git root
|
|
1232
|
+
file_path_for_git = file_path
|
|
1233
|
+
if os.path.isabs(file_path):
|
|
1234
|
+
# Get git root to make path relative if needed
|
|
1235
|
+
git_root_result = subprocess.run(
|
|
1236
|
+
["git", "-C", working_dir, "rev-parse", "--show-toplevel"],
|
|
1237
|
+
capture_output=True,
|
|
1238
|
+
text=True
|
|
1239
|
+
)
|
|
1240
|
+
|
|
1241
|
+
if git_root_result.returncode == 0:
|
|
1242
|
+
git_root = git_root_result.stdout.strip()
|
|
1243
|
+
try:
|
|
1244
|
+
file_path_for_git = os.path.relpath(file_path, git_root)
|
|
1245
|
+
self.logger.info(f"[GIT-STATUS-DEBUG] Made file path relative to git root: {file_path_for_git}")
|
|
1246
|
+
except ValueError:
|
|
1247
|
+
# File is not under git root - keep original path
|
|
1248
|
+
self.logger.info(f"[GIT-STATUS-DEBUG] File not under git root, keeping original path: {file_path}")
|
|
1249
|
+
pass
|
|
1250
|
+
|
|
1251
|
+
# Check if the file exists
|
|
1252
|
+
full_path = file_path if os.path.isabs(file_path) else os.path.join(working_dir, file_path)
|
|
1253
|
+
if not os.path.exists(full_path):
|
|
1254
|
+
self.logger.warning(f"[GIT-STATUS-DEBUG] File does not exist: {full_path}")
|
|
1255
|
+
await self.sio.emit('git_status_response', {
|
|
1256
|
+
'success': False,
|
|
1257
|
+
'error': f'File does not exist: {file_path}',
|
|
1258
|
+
'file_path': file_path,
|
|
1259
|
+
'working_dir': working_dir,
|
|
1260
|
+
'original_working_dir': original_working_dir
|
|
1261
|
+
}, room=sid)
|
|
1262
|
+
return
|
|
1263
|
+
|
|
1264
|
+
# Check git status for the file - this succeeds if git knows about the file
|
|
1265
|
+
# (either tracked, modified, staged, etc.)
|
|
1266
|
+
self.logger.info(f"[GIT-STATUS-DEBUG] Checking git status for file: {file_path_for_git}")
|
|
1267
|
+
git_status_result = subprocess.run(
|
|
1268
|
+
["git", "-C", working_dir, "status", "--porcelain", file_path_for_git],
|
|
1269
|
+
capture_output=True,
|
|
1270
|
+
text=True
|
|
1271
|
+
)
|
|
1272
|
+
|
|
1273
|
+
self.logger.info(f"[GIT-STATUS-DEBUG] Git status result: returncode={git_status_result.returncode}, stdout={repr(git_status_result.stdout)}, stderr={repr(git_status_result.stderr)}")
|
|
1274
|
+
|
|
1275
|
+
# Also check if file is tracked by git (alternative approach)
|
|
1276
|
+
ls_files_result = subprocess.run(
|
|
1277
|
+
["git", "-C", working_dir, "ls-files", file_path_for_git],
|
|
1278
|
+
capture_output=True,
|
|
1279
|
+
text=True
|
|
1280
|
+
)
|
|
1281
|
+
|
|
1282
|
+
is_tracked = ls_files_result.returncode == 0 and ls_files_result.stdout.strip()
|
|
1283
|
+
has_status = git_status_result.returncode == 0
|
|
1284
|
+
|
|
1285
|
+
self.logger.info(f"[GIT-STATUS-DEBUG] File tracking status: is_tracked={is_tracked}, has_status={has_status}")
|
|
1286
|
+
|
|
1287
|
+
# Success if git knows about the file (either tracked or has status changes)
|
|
1288
|
+
if is_tracked or has_status:
|
|
1289
|
+
self.logger.info(f"[GIT-STATUS-DEBUG] Git status check successful for {file_path}")
|
|
1290
|
+
await self.sio.emit('git_status_response', {
|
|
1291
|
+
'success': True,
|
|
1292
|
+
'file_path': file_path,
|
|
1293
|
+
'working_dir': working_dir,
|
|
1294
|
+
'original_working_dir': original_working_dir,
|
|
1295
|
+
'is_tracked': is_tracked,
|
|
1296
|
+
'has_changes': bool(git_status_result.stdout.strip()) if has_status else False
|
|
1297
|
+
}, room=sid)
|
|
1298
|
+
else:
|
|
1299
|
+
self.logger.info(f"[GIT-STATUS-DEBUG] File {file_path} is not tracked by git")
|
|
1300
|
+
await self.sio.emit('git_status_response', {
|
|
1301
|
+
'success': False,
|
|
1302
|
+
'error': 'File is not tracked by git',
|
|
1303
|
+
'file_path': file_path,
|
|
1304
|
+
'working_dir': working_dir,
|
|
1305
|
+
'original_working_dir': original_working_dir,
|
|
1306
|
+
'is_tracked': False
|
|
1307
|
+
}, room=sid)
|
|
1308
|
+
|
|
1309
|
+
except Exception as e:
|
|
1310
|
+
self.logger.error(f"[GIT-STATUS-DEBUG] Exception in check_git_status: {e}")
|
|
1311
|
+
import traceback
|
|
1312
|
+
self.logger.error(f"[GIT-STATUS-DEBUG] Stack trace: {traceback.format_exc()}")
|
|
1313
|
+
await self.sio.emit('git_status_response', {
|
|
1314
|
+
'success': False,
|
|
1315
|
+
'error': str(e),
|
|
1316
|
+
'file_path': data.get('file_path', 'unknown'),
|
|
1317
|
+
'working_dir': data.get('working_dir', 'unknown')
|
|
1318
|
+
}, room=sid)
|
|
1319
|
+
|
|
1143
1320
|
@self.sio.event
|
|
1144
1321
|
async def git_add_file(sid, data):
|
|
1145
1322
|
"""Add file to git tracking"""
|