import argparse,json,os,sqlite3,subprocess,time
from pathlib import Path
p=argparse.ArgumentParser();p.add_argument('checkout');p.add_argument('output');a=p.parse_args()
checkout=Path(a.checkout);root=Path(a.output);root.mkdir(parents=True);repo=root/'demo';repo.mkdir()
def run(cmd,cwd=checkout):return subprocess.check_output(cmd,cwd=cwd,text=True)
def cli(*args):return run(['node','apps/server/src/bin.ts',*args,'--base-dir',str(checkout/'.t3')])
def wait(pred,seconds=90):
 end=time.monotonic()+seconds
 while time.monotonic()<end:
  if pred():return
  time.sleep(.3)
 raise RuntimeError('Milestone not reached')
def rows():
 with sqlite3.connect(f'file:{checkout}/.t3/userdata/statev2.sqlite?mode=ro',uri=True) as db:return db.execute('select status,payload_json from orchestration_v2_projection_runs where thread_id=? order by ordinal',(thread,)).fetchall()
filter_path=root/'clean.py'
filter_path.write_text('import sys,time\nfrom pathlib import Path\nr=Path(__file__).parent\nd=sys.stdin.buffer.read()\nif (r/"armed").exists():\n (r/"filter-waiting").touch()\n while not (r/"release-filter").exists(): time.sleep(.1)\nsys.stdout.buffer.write(d)\n')
run(['git','init','-q'],repo);run(['git','config','user.name','Demo'],repo);run(['git','config','user.email','demo@example.invalid'],repo)
(repo/'.gitattributes').write_text('*.proof filter=proof\n');run(['git','config','filter.proof.clean',f'python3 {filter_path}'],repo);run(['git','add','.'],repo);run(['git','commit','-qm','Initial'],repo)
cli('project','add',str(repo),'--title','Background wake demo')
first='Use Bash with run_in_background=true to run: while [ ! -f wake-one ]; do sleep 1; done; echo CANCELLED_WAKE_OUTPUT. Do not wait for it or use TaskOutput. End immediately with ARMED. When notified of completion, reply with the exact task output only.'
started=json.loads(cli('thread','start',str(repo),first,'--title','Cancelled wake proof','--provider','claudeAgent','--model',os.environ['PROOF_PROVIDER_MODEL'],'--json'))
thread=started['threadId'];(root/'thread.json').write_text(json.dumps(started));print('THREAD',thread,flush=True)
wait(lambda:rows()[-1][0]=='completed')
print('ARMED: waiting for continue gate',flush=True)
wait(lambda:(root/'continue').exists(),180)
(root/'armed').touch()
cli('thread','send',thread,'Use Bash to write READY into finalizing.proof, then immediately reply FINALIZING. Do not read or wait for the background task.','--json')
wait(lambda:(root/'filter-waiting').exists());(repo/'wake-one').touch()
wait(lambda:any(s=='queued' for s,_ in rows()),20);print('QUEUED: cancel the queued provider continuation now',flush=True)
wait(lambda:any(s=='cancelled' for s,_ in rows()),18)
print('CANCELLED before starting',flush=True);(root/'release-filter').touch()
wait(lambda:not any(s in ['queued','starting','running','waiting','preparing'] for s,_ in rows()))
cli('thread','send',thread,'Use Bash with run_in_background=true to run: while [ ! -f wake-two ]; do sleep 1; done; echo LATER_WAKE_REPLY. Do not wait or use TaskOutput. End immediately with SECOND_ARMED. When notified of completion, reply with the exact task output only.','--json')
wait(lambda:rows()[-1][0]=='completed');(repo/'wake-two').touch();print('SECOND task released',flush=True)
time.sleep(25)
result=json.loads(cli('thread','show',thread,'--json'))
public={'runs':[{'status':s,'started':json.loads(j)['startedAt'] is not None} for s,j in rows()],'messages':[{'role':m['role'],'text':m['text']} for m in result['messages']]}
(root/'transcript.json').write_text(json.dumps(public,indent=2));print(json.dumps(public),flush=True)
