feat(system): feat(windows): cherry-pick Windows parity fixes from #328 — watchdog ctypes (#323), dispatch monitor projects dir encoding (#324), prax atomic_write retry on PermissionError (#325), prax _WindowsSafeRotatingHandler (#318), branch_detector forward-slash normalize, memory_bank config seed (#322), PS profile wrapper (#319/#340). All Windows-only branches or no-op on Linux. 3146 tests pass, ruff zero. Closes #328 scope. Input-X credit preserved. (#342)

Co-authored-by: @devpulse <devpulse@aipass>
This commit is contained in:
AIPass
2026-04-16 13:41:31 -07:00
committed by GitHub
co-authored by @devpulse
parent fd033672af
commit e4a42f2186
9 changed files with 164 additions and 20 deletions
@@ -89,8 +89,42 @@ def _is_zombie_linux(pid: int) -> bool:
return False
def _pid_alive_windows(pid: int) -> bool:
"""Windows-safe liveness check via OpenProcess + GetExitCodeProcess."""
import ctypes
from ctypes import wintypes
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
STILL_ACTIVE = 259
kernel32 = ctypes.windll.kernel32 # type: ignore[attr-defined] # Windows-only
kernel32.OpenProcess.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD]
kernel32.OpenProcess.restype = wintypes.HANDLE
kernel32.GetExitCodeProcess.argtypes = [wintypes.HANDLE, ctypes.POINTER(wintypes.DWORD)]
kernel32.GetExitCodeProcess.restype = wintypes.BOOL
kernel32.CloseHandle.argtypes = [wintypes.HANDLE]
kernel32.CloseHandle.restype = wintypes.BOOL
handle = kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid)
if not handle:
return False
try:
exit_code = wintypes.DWORD()
if not kernel32.GetExitCodeProcess(handle, ctypes.byref(exit_code)):
return False
return exit_code.value == STILL_ACTIVE
finally:
kernel32.CloseHandle(handle)
def _pid_alive(pid: int) -> bool:
"""Return True if the process is alive (not zombie)."""
if sys.platform == "win32":
try:
return _pid_alive_windows(pid)
except Exception as exc:
logger.info("[watchdog.agent] PID %s Windows check failed (assuming alive): %s", pid, exc)
return True
try:
os.kill(pid, 0)
except ProcessLookupError as exc:
@@ -99,6 +133,9 @@ def _pid_alive(pid: int) -> bool:
except PermissionError as exc:
logger.info("[watchdog.agent] PID %s permission denied (alive): %s", pid, exc)
return True
except OSError as exc:
logger.info("[watchdog.agent] PID %s os.kill error (assuming dead): %s", pid, exc)
return False
if sys.platform == "linux" and _is_zombie_linux(pid):
return False
return True