Command: powershell -NoProfile -Command "$root='C:\Users\ash\Documents\Codex\2026-08-26\LaptopBridge-DEV';Write-Output '---START---';Get-Content -LiteralPath (Join-Path $root 'start-ha3-dev.ps1');Write-Output '---SUPERVISOR---';Get-Content -LiteralPath (Join-Path $root 'ha3_supervisor.py') | Select-Object -First 320"
Directory: projects
Status: SUCCESS
Exit code: 0
Cancel Job Rerun Command Refresh
---START---
$ErrorActionPreference = "Stop"
$root = Split-Path -Parent $MyInvocation.MyCommand.Path
$runtime = Get-Content -Raw -LiteralPath (Join-Path $root "ha3_runtime.json") | ConvertFrom-Json
$data = Join-Path $root "bridge-data"
New-Item -ItemType Directory -Force -Path $data | Out-Null
$pidFile = Join-Path $data "ha3-supervisor.pid"
if (Test-Path $pidFile) {
$oldPid = 0
[void][int]::TryParse((Get-Content -Raw $pidFile).Trim(), [ref]$oldPid)
if ($oldPid -gt 0) {
$p = Get-Process -Id $oldPid -ErrorAction SilentlyContinue
if ($p) { Write-Output "HA3_SUPERVISOR_ALREADY_RUNNING"; exit 0 }
}
}
$python = (Get-Command python.exe -ErrorAction Stop).Source
Start-Process -FilePath $python -ArgumentList "ha3_supervisor.py" -WorkingDirectory $root -WindowStyle Hidden
Write-Output ("HA3_SUPERVISOR_START_REQUESTED port=" + $runtime.port)
---SUPERVISOR---
from pathlib import Path
import json, os, sys, time, subprocess, urllib.request, socket
ROOT=Path(__file__).resolve().parent
RUNTIME_PATH=ROOT/'ha3_runtime.json';DATA=ROOT/'bridge-data';DATA.mkdir(parents=True,exist_ok=True)
SUP_PID=DATA/'ha3-supervisor.pid';CHILD_PID=DATA/'ha3-child.pid';STATUS=DATA/'ha3-status.json';HEARTBEAT=DATA/'ha3-drive-worker-heartbeat.json';STDOUT=DATA/'ha3-dev.stdout.log';STDERR=DATA/'ha3-dev.stderr.log'
def load_runtime(): return json.loads(RUNTIME_PATH.read_text(encoding='utf-8-sig'))
def atomic_json(p,o):
t=p.with_suffix(p.suffix+'.tmp');t.write_text(json.dumps(o,separators=(',',':')),encoding='utf-8');t.replace(p)
def pid_exists(pid):
try:
import psutil;return psutil.pid_exists(int(pid))
except Exception:return False
def read_pid(p):
try:return int(p.read_text().strip())
except Exception:return None
def health(port,timeout):
try:
with urllib.request.urlopen(f'http://127.0.0.1:{int(port)}/health',timeout=float(timeout)) as r:
b=r.read(64).decode('utf-8','replace').strip();return r.status==200 and b=='OK',b
except Exception as e:return False,type(e).__name__
def hb_state(max_age,child):
try:
d=json.loads(HEARTBEAT.read_text(encoding='utf-8'));age=time.time()-float(d.get('epoch',0));match=int(d.get('pid',-1))==int(child or -2);return age<=float(max_age) and match,round(age,3),d,match
except Exception as e:return False,None,{'error':type(e).__name__},False
def port_free(port):
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
try:s.bind(('127.0.0.1',int(port)));return True
except OSError:return False
finally:s.close()
def choose_port(pool,current=None):
pool=[int(x) for x in pool];order=pool
if current in pool:order=pool[pool.index(current)+1:]+pool[:pool.index(current)+1]
for p in order:
if port_free(p):return p
return None
def async_kill(pid):
if not pid:return
try:
import psutil;p=psutil.Process(int(pid));cmd=' '.join(p.cmdline()).lower()
if 'dev_bridge_launcher.py' not in cmd:return
flags=getattr(subprocess,'CREATE_NO_WINDOW',0)
subprocess.Popen(['taskkill','/PID',str(int(pid)),'/T','/F'],stdin=subprocess.DEVNULL,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,creationflags=flags,close_fds=True)
except Exception:pass
def spawn(rt,port):
env=os.environ.copy();env['CHATGPT_BRIDGE_DRIVE_ROOT']=str(rt['drive_root']);env['CHATGPT_BRIDGE_PORT']=str(int(port))
flags=getattr(subprocess,'CREATE_NO_WINDOW',0)|getattr(subprocess,'CREATE_NEW_PROCESS_GROUP',0)
out=open(STDOUT,'ab',buffering=0);err=open(STDERR,'ab',buffering=0)
p=subprocess.Popen([sys.executable,str(ROOT/'dev_bridge_launcher.py')],cwd=str(ROOT),env=env,stdin=subprocess.DEVNULL,stdout=out,stderr=err,creationflags=flags,close_fds=True)
CHILD_PID.write_text(str(p.pid),encoding='ascii');return p.pid
def write_status(rt,child,port,healthy,detail,restarts,failovers):atomic_json(STATUS,{'revision':rt['revision'],'epoch':time.time(),'supervisor_pid':os.getpid(),'child_pid':child,'active_port':port,'drive_root':str(rt['drive_root']),'healthy':bool(healthy),'detail':detail,'restarts':restarts,'failovers':failovers})
def main():
SUP_PID.write_text(str(os.getpid()),encoding='ascii');rt=load_runtime();pool=[int(x) for x in rt.get('port_pool',[rt.get('port',8767)])];child=read_pid(CHILD_PID);active=None;restarts=0;failovers=0;started=0.0
# Never trust inherited child without a known healthy port; select a free port for v2.
active=choose_port(pool,None)
if active is None:raise SystemExit('HA3_NO_FREE_PORT')
child=spawn(rt,active);started=time.time();restarts+=1
while True:
rt=load_runtime();pool=[int(x) for x in rt.get('port_pool',pool)];timeout=float(rt.get('health_timeout_seconds',2));stale=float(rt.get('heartbeat_stale_seconds',10));grace=float(rt.get('startup_grace_seconds',10));interval=float(rt.get('monitor_interval_seconds',1.5));cool=float(rt.get('restart_cooldown_seconds',.5))
alive=bool(child and pid_exists(child));hok,hdetail=health(active,timeout);hbok,hbage,hb,hbmatch=hb_state(stale,child);ingrace=(time.time()-started)<grace;healthy=alive and hok and (hbok or ingrace)
write_status(rt,child,active,healthy,{'child_alive':alive,'http_ok':hok,'http_detail':hdetail,'heartbeat_ok':hbok,'heartbeat_age':hbage,'heartbeat_pid_match':hbmatch,'startup_grace':ingrace},restarts,failovers)
if not healthy and not ingrace:
async_kill(child);time.sleep(cool);nxt=choose_port(pool,active)
if nxt is not None:
if nxt!=active:failovers+=1
active=nxt;child=spawn(rt,active);started=time.time();restarts+=1
time.sleep(interval)
if __name__=='__main__':main()