Bump to v1.5.0: deduplicate detectors, fix aggregator bugs, fix blocking I/O

- Extract shared send_event/clear_event into detectors/base.py, removing
  ~150 lines of duplication across all 6 detectors
- Fix default aggregator URL from port 5000 to 5100 in all detectors
- Standardize cpu.py and memory.py to use active_alerts set pattern
- Fix immediate emote rotation on startup (last_emote_change = time.time())
- Extract magic numbers to named constants in aggregator
- Protect write_status() with try/except OSError
- Fix notify event ID collision with monotonic counter
- Replace blocking stream_output() with background daemon threads in kao.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-06 12:17:17 -06:00
parent c3ceb74ce8
commit dd8bf6005b
12 changed files with 126 additions and 236 deletions

21
kao.py
View File

@@ -11,6 +11,7 @@ import os
import signal
import subprocess
import sys
import threading
import time
from pathlib import Path
@@ -59,11 +60,19 @@ class KaoManager:
universal_newlines=True,
)
print(f"[{name}] Started (PID {process.pid})")
# Read output in a background thread to avoid blocking the main loop
thread = threading.Thread(target=self._read_output, args=(name, process), daemon=True)
thread.start()
return process
except Exception as e:
print(f"[{name}] Failed to start: {e}")
return None
def _read_output(self, name, process):
"""Read and print output from a process in a background thread."""
for line in process.stdout:
print(f"[{name}] {line.rstrip()}")
def wait_for_aggregator(self, url, timeout=AGGREGATOR_STARTUP_TIMEOUT):
"""Wait for the aggregator to become available."""
print(f"[aggregator] Waiting for service at {url}...")
@@ -80,15 +89,6 @@ class KaoManager:
print(f"[aggregator] Timeout waiting for service")
return False
def stream_output(self, name, process):
"""Read and print output from a process (non-blocking)."""
if process.stdout:
while True:
line = process.stdout.readline()
if not line:
break
print(f"[{name}] {line.rstrip()}")
def get_aggregator_url(self):
"""Get aggregator URL from config port."""
port = self.config.get("port", 5100)
@@ -135,9 +135,6 @@ class KaoManager:
for name, info in list(self.processes.items()):
process = info["process"]
# Stream any available output
self.stream_output(name, process)
# Check if process has exited
if process.poll() is not None:
print(f"[{name}] Exited with code {process.returncode}, restarting in {RESTART_DELAY}s...")