plan: queue proxy and ghost follow-up phases
This commit is contained in:
+24
-17
@@ -152,6 +152,13 @@ def session_alive(name):
|
||||
capture_output=True
|
||||
).returncode == 0
|
||||
|
||||
def session_command(name):
|
||||
r = subprocess.run(
|
||||
["tmux", "display-message", "-p", "-t", name, "#{pane_current_command}"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
return r.stdout.strip() if r.returncode == 0 else ""
|
||||
|
||||
def kill_session(name):
|
||||
subprocess.run(["tmux", "kill-session", "-t", name], capture_output=True)
|
||||
|
||||
@@ -573,26 +580,21 @@ def stall_check():
|
||||
# ── orchestrator healing ──────────────────────────────────────────────────────
|
||||
|
||||
def orchestrator_alive():
|
||||
"""True only for the configured orchestrator tmux session.
|
||||
|
||||
Older versions treated any non-loop Claude process as an orchestrator, which meant idle
|
||||
report/assistant sessions could mask a missing or stale orchestrator. The watchdog wakes
|
||||
and heals ORCH_SESSION, so ORCH_SESSION is the only valid liveness signal here.
|
||||
"""
|
||||
True if an orchestrator process is running anywhere.
|
||||
Conflict-safety: never launch a second orchestrator resuming the same session
|
||||
(double-resume causes "thinking blocks cannot be modified" crashes).
|
||||
"""
|
||||
for line in subprocess.run("pgrep -x claude || true", shell=True,
|
||||
capture_output=True, text=True).stdout.splitlines():
|
||||
pid = line.strip()
|
||||
if not pid:
|
||||
continue
|
||||
try:
|
||||
cmdline = Path(f"/proc/{pid}/cmdline").read_bytes().decode(errors="replace").replace("\0", " ")
|
||||
# Skip the loop sessions and the upgrader — they're not the orchestrator.
|
||||
if re.search(r"--remote-control\s+'?cc-ci-(builder|adv|upgrader)'?", cmdline):
|
||||
continue
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return session_alive(ORCH_SESSION)
|
||||
|
||||
def orchestrator_backend_mismatch():
|
||||
cmd = session_command(ORCH_SESSION)
|
||||
if not cmd:
|
||||
return False
|
||||
expected = "opencode" if BACKEND == "opencode" else "claude"
|
||||
return cmd != expected
|
||||
|
||||
def heal_orchestrator():
|
||||
if not WATCH_ORCHESTRATOR:
|
||||
return
|
||||
@@ -601,6 +603,11 @@ def heal_orchestrator():
|
||||
|
||||
if orchestrator_alive():
|
||||
if session_alive(ORCH_SESSION):
|
||||
if orchestrator_backend_mismatch():
|
||||
log(f"orchestrator ({ORCH_SESSION}) is {session_command(ORCH_SESSION)!r}, expected {BACKEND} — kill + restart")
|
||||
kill_session(ORCH_SESSION)
|
||||
subprocess.run([ORCH_LAUNCHER, "start"], capture_output=True)
|
||||
return
|
||||
pane = capture_pane(ORCH_SESSION, 25)
|
||||
if ACTIVE_RE.search(pane):
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user