import os,sys,json,time,pathlib,subprocess,signal,sqlite3,datetime,socket,re
ROOT=pathlib.Path(__file__).resolve().parent
side,checkout,selection=sys.argv[1:4]
run=ROOT/(datetime.datetime.now(datetime.timezone.utc).strftime('%Y%m%dT%H%M%SZ')+'-'+side+'-'+selection)
run.mkdir(); home=run/'home'; (home/'userdata').mkdir(parents=True)
repo=run/'demo'; repo.mkdir()
def cmd(args,**kw):
 return subprocess.run(args,text=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,check=True,**kw).stdout
cmd(['git','init','-b','main',str(repo)])
(repo/'package.json').write_text('{"name":"setup-demo","private":true,"scripts":{"start":"node index.js"}}\n')
(repo/'index.js').write_text('console.log("Setup demo");\n')
cmd(['git','-C',str(repo),'add','.']);cmd(['git','-C',str(repo),'-c','user.name=Demo','-c','user.email=demo@example.invalid','commit','-m','Initial demo'])
# These wrappers record only provider identity and operation, then exec the real CLI unchanged.
for instance,binary in [('codex','codex'),('customClaude','claude')]:
 real=cmd(['which',binary]).strip(); wrapper=run/instance
 wrapper.write_text('''#!/usr/bin/env python3
import os,sys,json,datetime,pathlib
args=sys.argv[1:]
operation='probe or session'
try:
 if '--json-schema' in args:
  schema=json.loads(args[args.index('--json-schema')+1]);operation='branch-name generation' if 'branch' in schema.get('properties',{}) else 'other text generation'
 elif '--output-schema' in args:
  schema=json.loads(pathlib.Path(args[args.index('--output-schema')+1]).read_text());operation='branch-name generation' if 'branch' in schema.get('properties',{}) else 'other text generation'
except Exception: operation='schema unreadable'
with open('''+repr(str(run/'invocations.jsonl'))+''','a') as f:
 f.write(json.dumps({'time':datetime.datetime.now(datetime.timezone.utc).isoformat(),'instance':''' + repr(instance)+''','operation':operation})+'\\n')
os.execv('''+repr(real)+''',['''+repr(real)+''']+args)
''');wrapper.chmod(0o755)
settings={'providerInstances':{d:{'driver':d,'enabled':False,'config':{}} for d in ['codex','claudeAgent','cursor','grok','muse','pi','opencode','antigravity','fx','kiro']},'enableProviderUpdateChecks':False}
settings['providerInstances']['codex']['config']['binaryPath']=str(run/'codex')
settings['providerInstances']['customClaude']={'driver':'claudeAgent','enabled':True,'config':{'binaryPath':str(run/'customClaude')}}
if selection=='explicit': settings['textGenerationModelSelection']={'instanceId':'codex','model':os.environ['CODEX_TEXT_MODEL']}
(home/'userdata'/'settings.json').write_text(json.dumps(settings))
env=dict(os.environ,T3CODE_DEV_SEED='0');env.pop('T3CODE_HOME',None)
serverlog=open(run/'server.log','w')
sock=socket.socket();sock.bind(('127.0.0.1',0));port=str(sock.getsockname()[1]);sock.close()
server=subprocess.Popen(['node','apps/server/src/bin.ts','serve','--base-dir',str(home),'--port',port,'--host','127.0.0.1'],cwd=checkout,env=env,stdout=serverlog,stderr=subprocess.STDOUT,start_new_session=True)
(run/'pid').write_text(str(server.pid))
print('RUN',run,flush=True)
try:
 for _ in range(120):
  if list(home.rglob('server-runtime.json')):break
  if server.poll() is not None:raise RuntimeError('server exited: '+(run/'server.log').read_text()[-4000:])
  time.sleep(1)
 else:raise RuntimeError('server startup timeout')
 cli=['node','apps/server/src/bin.ts']
 out=cmd(cli+['project','add',str(repo),'--title','Setup demo','--base-dir',str(home)],cwd=checkout,env=env);(run/'project.log').write_text(out)
 out=cmd(cli+['thread','start',str(repo),'Add a README describing the project setup. Keep it short.','--worktree','main','--provider','customClaude','--model',os.environ['CLAUDE_TURN_MODEL'],'--json','--base-dir',str(home)],cwd=checkout,env=env);(run/'launch.log').write_text(out)
 print('Launched thread',flush=True)
 # Observe the actual subprocess invocation and resulting Git branch, not a test double.
 for _ in range(240):
  log=(run/'invocations.jsonl').read_text() if (run/'invocations.jsonl').exists() else ''
  branches=cmd(['git','-C',str(repo),'branch','--format=%(refname:short)']).splitlines()
  if 'branch-name generation' in log and any(b!='main' and not b.startswith('t3code/') for b in branches):
   # Give the background rename a chance; a temporary branch is recorded separately.
   if any(b!='main' and not re.fullmatch(r't3/[a-f0-9]{8}',b) for b in branches): break
  time.sleep(1)
 (run/'branches.json').write_text(json.dumps(branches))
 print(log,flush=True); print('Branches:',branches,flush=True)
 # Let the short real agent turn end before shutting down the isolated server.
 time.sleep(8)
finally:
 os.killpg(server.pid,signal.SIGTERM)
 try:server.wait(timeout=20)
 except subprocess.TimeoutExpired:os.killpg(server.pid,signal.SIGKILL);server.wait()
 serverlog.close()
