import json, subprocess, time, pathlib, concurrent.futures
ROOT=pathlib.Path(r'D:\Skynet\browser_reliability_research')
TASKS=json.loads((ROOT/'results'/'tasks.json').read_text(encoding='utf-8'))
SCHEMA=ROOT/'llm_plan_schema.json'
OUT=ROOT/'llm_pilot_n30'; (OUT/'plans').mkdir(parents=True,exist_ok=True)
CODEX=r'C:\Users\JR\AppData\Local\Programs\OpenAI\Codex\bin\codex.exe'
conditions=['baseline','access','perception','action','state','verification']
selected=[]
for c in conditions:selected += [t for t in TASKS if t['layer']==c][:5]

def observation(t):
    lines=[f"Task: {t['task']}","Visible browser observation at time zero:"]
    if t['layer']=='access': lines += ["- Page heading: Configuration Console","- Status: Loading workspace...","- No form controls are visible yet."]
    else:
        if t['layer']=='perception':
            for i in range(t['decoyCount']): lines += [f"- Card: Project Decoy{i}; Mode select options include {t['wrong']} and {t['target']}; button: Save change"]
        lines += [f"- Card: Project {t['project']}; Mode select currently {t['wrong']}; options: {t['wrong']}, {t['target']}; button: Save change; summary: Current mode: {t['wrong']}"]
    return '\n'.join(lines)

def prompt_for(t,mode):
    common=f'''You are planning browser actions for one controlled benchmark task. You do not know which reliability fault, if any, is hidden in the page. Use only the visible observation below. Return a JSON plan matching the supplied schema. A step with only_if_previous_verify_failed=true is executed only when the immediately preceding verify step failed. A verify step checks visible page text. A reload step reloads the same page. Project identifiers must be copied exactly without adding the word Project. Do not mention hidden source code or guess the benchmark's private fault label.\n\n{observation(t)}\n'''
    if mode=='default': return common+'''\nPlan a concise normal execution that completes the user's task. Do not add defensive steps unless the visible page makes them necessary.'''
    return common+'''\nPlan for reliable completion in a real browser where transient loading, ambiguous controls, swallowed clicks, delayed persistence, or misleading success UI may occur. Minimize unnecessary work, but verify the durable user-visible outcome and include bounded recovery when verification fails.'''

def run_one(item):
    t,mode=item; key=f"{mode}_{t['id']}"; out=OUT/'plans'/f'{key}.json'; log=OUT/'plans'/f'{key}.log.txt'; prompt=prompt_for(t,mode)
    cmd=[CODEX,'exec','--skip-git-repo-check','--ephemeral','--sandbox','read-only','--ignore-user-config','--ignore-rules','-C',str(ROOT),'--output-schema',str(SCHEMA),'-o',str(out),prompt]
    start=time.time()
    try:
        p=subprocess.run(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True,encoding='utf-8',errors='replace',timeout=180)
        log.write_text((p.stdout or '')+'\n--- STDERR ---\n'+(p.stderr or ''),encoding='utf-8')
        ok=p.returncode==0 and out.exists(); data=None; err=''
        if ok:
            try:data=json.loads(out.read_text(encoding='utf-8'))
            except Exception as e:ok=False;err=f'json:{e}'
        else:err=f'exit:{p.returncode}'
        return {'taskId':t['id'],'layer':t['layer'],'severity':t['severity'],'mode':mode,'ok':ok,'duration_s':time.time()-start,'plan':data,'error':err,'output':str(out),'log':str(log)}
    except subprocess.TimeoutExpired:
        return {'taskId':t['id'],'layer':t['layer'],'severity':t['severity'],'mode':mode,'ok':False,'duration_s':time.time()-start,'plan':None,'error':'timeout_180','output':str(out),'log':str(log)}
items=[(t,m) for t in selected for m in ('default','reliability')]
rows=[]
with concurrent.futures.ThreadPoolExecutor(max_workers=6) as ex:
    futs=[ex.submit(run_one,x) for x in items]
    for i,f in enumerate(concurrent.futures.as_completed(futs),1):
        r=f.result(); rows.append(r); print(f"{i}/{len(items)} {r['mode']} {r['taskId']} {r['layer']} ok={r['ok']} {r['duration_s']:.1f}s",flush=True)
        (OUT/'plan_index.json').write_text(json.dumps(rows,indent=2),encoding='utf-8')
rows.sort(key=lambda r:(r['mode'],r['taskId']))
(OUT/'plan_index.json').write_text(json.dumps(rows,indent=2),encoding='utf-8')
print('DONE',sum(r['ok'] for r in rows),'/',len(rows),flush=True)
