claude-mpm 4.2.28__py3-none-any.whl → 4.2.32__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.
@@ -24,6 +24,7 @@ from typing import Optional
24
24
 
25
25
  from ...core.logging_config import get_logger
26
26
  from ..hook_installer_service import HookInstallerService
27
+ from .daemon_manager import DaemonManager
27
28
  from .management.health import HealthMonitor
28
29
  from .management.lifecycle import DaemonLifecycle
29
30
  from .server import UnifiedMonitorServer
@@ -58,7 +59,15 @@ class UnifiedMonitorDaemon:
58
59
  self.daemon_mode = daemon_mode
59
60
  self.logger = get_logger(__name__)
60
61
 
61
- # Daemon management with port for verification
62
+ # Use new consolidated DaemonManager for all daemon operations
63
+ self.daemon_manager = DaemonManager(
64
+ port=port,
65
+ host=host,
66
+ pid_file=pid_file or self._get_default_pid_file(),
67
+ log_file=log_file,
68
+ )
69
+
70
+ # Keep lifecycle for backward compatibility (delegates to daemon_manager)
62
71
  self.lifecycle = DaemonLifecycle(
63
72
  pid_file=pid_file or self._get_default_pid_file(),
64
73
  log_file=log_file,
@@ -104,84 +113,13 @@ class UnifiedMonitorDaemon:
104
113
 
105
114
  def _cleanup_port_conflicts(self) -> bool:
106
115
  """Try to clean up any processes using our port.
107
-
116
+
117
+ Delegates to the consolidated DaemonManager for consistent behavior.
118
+
108
119
  Returns:
109
120
  True if cleanup was successful, False otherwise
110
121
  """
111
- try:
112
- # Find process using the port
113
- import subprocess
114
- result = subprocess.run(
115
- ["lsof", "-ti", f":{self.port}"],
116
- capture_output=True,
117
- text=True
118
- )
119
-
120
- if result.returncode == 0 and result.stdout.strip():
121
- pids = result.stdout.strip().split('\n')
122
- for pid_str in pids:
123
- try:
124
- pid = int(pid_str.strip())
125
- self.logger.info(f"Found process {pid} using port {self.port}")
126
-
127
- # Check if it's a claude-mpm process
128
- process_info = subprocess.run(
129
- ["ps", "-p", str(pid), "-o", "comm="],
130
- capture_output=True,
131
- text=True
132
- )
133
-
134
- if "python" in process_info.stdout.lower() or "claude" in process_info.stdout.lower():
135
- self.logger.info(f"Killing process {pid} (appears to be Python/Claude related)")
136
- os.kill(pid, signal.SIGTERM)
137
- time.sleep(1)
138
-
139
- # Check if still alive
140
- try:
141
- os.kill(pid, 0)
142
- # Still alive, force kill
143
- self.logger.warning(f"Process {pid} didn't terminate, force killing")
144
- os.kill(pid, signal.SIGKILL)
145
- time.sleep(1)
146
- except ProcessLookupError:
147
- pass
148
- else:
149
- self.logger.warning(f"Process {pid} is not a Claude MPM process: {process_info.stdout}")
150
- return False
151
- except (ValueError, ProcessLookupError) as e:
152
- self.logger.debug(f"Error handling PID {pid_str}: {e}")
153
- continue
154
-
155
- return True
156
-
157
- except FileNotFoundError:
158
- # lsof not available, try alternative method
159
- self.logger.debug("lsof not available, using alternative cleanup")
160
-
161
- # Check if there's an orphaned service we can identify
162
- is_ours, pid = self.lifecycle.is_our_service(self.host)
163
- if is_ours and pid:
164
- try:
165
- self.logger.info(f"Killing orphaned Claude MPM service (PID: {pid})")
166
- os.kill(pid, signal.SIGTERM)
167
- time.sleep(1)
168
-
169
- # Check if still alive
170
- try:
171
- os.kill(pid, 0)
172
- os.kill(pid, signal.SIGKILL)
173
- time.sleep(1)
174
- except ProcessLookupError:
175
- pass
176
-
177
- return True
178
- except Exception as e:
179
- self.logger.error(f"Failed to kill process: {e}")
180
-
181
- except Exception as e:
182
- self.logger.error(f"Error during port cleanup: {e}")
183
-
184
- return False
122
+ return self.daemon_manager.cleanup_port_conflicts()
185
123
 
186
124
  def _start_daemon(self, force_restart: bool = False) -> bool:
187
125
  """Start as background daemon process.
@@ -191,108 +129,60 @@ class UnifiedMonitorDaemon:
191
129
  """
192
130
  self.logger.info("Starting unified monitor daemon in background mode")
193
131
 
194
- # Check if already running
195
- if self.lifecycle.is_running():
196
- existing_pid = self.lifecycle.get_pid()
197
-
198
- if force_restart:
199
- # Check if it's our service
200
- self.logger.debug(
201
- f"Checking if existing daemon (PID: {existing_pid}) is our service..."
202
- )
203
- is_ours, detected_pid = self.lifecycle.is_our_service(self.host)
132
+ # Always use daemon manager for cleanup first
133
+ # This ensures consistent behavior and prevents race conditions
134
+ if force_restart:
135
+ self.logger.info(
136
+ "Force restart requested, cleaning up any existing processes..."
137
+ )
138
+ if not self.daemon_manager.cleanup_port_conflicts(max_retries=3):
139
+ self.logger.error(f"Failed to clean up port {self.port}")
140
+ return False
141
+ # Wait for port to be fully released
142
+ time.sleep(2)
204
143
 
205
- if is_ours:
206
- self.logger.info(
207
- f"Force restarting our existing claude-mpm monitor daemon (PID: {detected_pid or existing_pid})"
208
- )
209
- # Stop the existing daemon
210
- if self.lifecycle.stop_daemon():
211
- # Wait a moment for port to be released
212
- time.sleep(2)
213
- else:
214
- self.logger.error("Failed to stop existing daemon for restart")
215
- return False
216
- else:
217
- self.logger.warning(
218
- f"Port {self.port} is in use by another service (PID: {existing_pid}). Cannot force restart."
219
- )
220
- self.logger.info(
221
- "To restart the claude-mpm monitor, first stop the other service or use a different port."
222
- )
223
- return False
224
- else:
144
+ # Check if already running via daemon manager
145
+ if self.daemon_manager.is_running():
146
+ existing_pid = self.daemon_manager.get_pid()
147
+ if not force_restart:
225
148
  self.logger.warning(f"Daemon already running with PID {existing_pid}")
226
149
  return False
150
+ # Force restart was already handled above
227
151
 
228
- # Check for orphaned processes (service running but no PID file)
229
- elif force_restart:
230
- self.logger.debug(
231
- "No PID file found, checking for orphaned claude-mpm service..."
152
+ # Check for our service on the port
153
+ is_ours, pid = self.daemon_manager.is_our_service()
154
+ if is_ours and pid and not force_restart:
155
+ self.logger.warning(
156
+ f"Our service already running on port {self.port} (PID: {pid})"
232
157
  )
233
- is_ours, pid = self.lifecycle.is_our_service(self.host)
234
- if is_ours and pid:
235
- self.logger.info(
236
- f"Found orphaned claude-mpm monitor service (PID: {pid}), force restarting"
237
- )
238
- # Try to kill the orphaned process
239
- try:
240
- os.kill(pid, signal.SIGTERM)
241
- # Wait for it to exit
242
- for _ in range(10):
243
- try:
244
- os.kill(pid, 0) # Check if still exists
245
- time.sleep(0.5)
246
- except ProcessLookupError:
247
- break
248
- else:
249
- # Force kill if still running
250
- os.kill(pid, signal.SIGKILL)
251
- time.sleep(1)
252
- except Exception as e:
253
- self.logger.error(f"Failed to kill orphaned process: {e}")
254
- return False
255
-
256
- # Check port availability and clean up if needed
257
- port_available, error_msg = self.lifecycle.verify_port_available(self.host)
258
- if not port_available:
259
- self.logger.warning(f"Port {self.port} is not available: {error_msg}")
260
-
261
- # Try to identify and kill any process using the port
262
- self.logger.info("Attempting to clean up processes on port...")
263
- cleaned = self._cleanup_port_conflicts()
264
-
265
- if cleaned:
266
- # Wait longer for port to be released to avoid race conditions
267
- time.sleep(3)
268
- # Check again
269
- port_available, error_msg = self.lifecycle.verify_port_available(self.host)
270
-
271
- if not port_available:
272
- self.logger.error(f"Port {self.port} is still not available after cleanup: {error_msg}")
273
- print(f"Error: {error_msg}", file=sys.stderr)
274
- print(f"Try 'claude-mpm monitor stop' or use --force flag", file=sys.stderr)
275
- return False
158
+ return False
276
159
 
277
160
  # Wait for any pre-warming threads to complete before forking
278
161
  self._wait_for_prewarm_completion()
279
162
 
280
- # Daemonize the process
281
- success = self.lifecycle.daemonize()
163
+ # Use daemon manager's daemonize which includes cleanup
164
+ self.daemon_manager.startup_status_file = None # Reset status file
165
+ success = self.daemon_manager.daemonize()
282
166
  if not success:
283
167
  return False
284
168
 
169
+ # We're now in the daemon process
170
+ # Update our PID references
171
+ self.lifecycle.pid_file = self.daemon_manager.pid_file
172
+
285
173
  # Start the server in daemon mode
286
- # This will run in the child process
287
174
  try:
288
175
  result = self._run_server()
289
176
  if not result:
290
177
  # Report failure before exiting
291
- self.lifecycle._report_startup_error("Failed to start server")
178
+ self.daemon_manager._report_startup_error("Failed to start server")
179
+ else:
180
+ # Report success
181
+ self.daemon_manager._report_startup_success()
292
182
  return result
293
183
  except Exception as e:
294
184
  # Report any exceptions during startup
295
- self.lifecycle._report_startup_error(f"Server startup exception: {e}")
185
+ self.daemon_manager._report_startup_error(f"Server startup exception: {e}")
296
186
  raise
297
187
 
298
188
  def _start_foreground(self, force_restart: bool = False) -> bool:
@@ -302,12 +192,12 @@ class UnifiedMonitorDaemon:
302
192
  force_restart: If True, restart existing service if it's ours
303
193
  """
304
194
  self.logger.info(f"Starting unified monitor daemon on {self.host}:{self.port}")
305
-
306
- # Clean up any processes on the port before checking service status
195
+
196
+ # Use daemon manager for consistent port cleanup
307
197
  # This helps with race conditions where old processes haven't fully released the port
308
198
  if force_restart:
309
199
  self.logger.info("Force restart requested, cleaning up port conflicts...")
310
- self._cleanup_port_conflicts()
200
+ self.daemon_manager.cleanup_port_conflicts(max_retries=2)
311
201
  time.sleep(1) # Brief pause to ensure port is released
312
202
 
313
203
  # Check if already running (check PID file even in foreground mode)
@@ -319,7 +209,7 @@ class UnifiedMonitorDaemon:
319
209
  self.logger.debug(
320
210
  f"Checking if existing daemon (PID: {existing_pid}) is our service..."
321
211
  )
322
- is_ours, detected_pid = self.lifecycle.is_our_service(self.host)
212
+ is_ours, detected_pid = self.daemon_manager.is_our_service()
323
213
 
324
214
  if is_ours:
325
215
  self.logger.info(
@@ -351,7 +241,7 @@ class UnifiedMonitorDaemon:
351
241
  self.logger.debug(
352
242
  "No PID file found, checking for orphaned claude-mpm service..."
353
243
  )
354
- is_ours, pid = self.lifecycle.is_our_service(self.host)
244
+ is_ours, pid = self.daemon_manager.is_our_service()
355
245
  if is_ours and pid:
356
246
  self.logger.info(
357
247
  f"Found orphaned claude-mpm monitor service (PID: {pid}), force restarting"