LazyBoy2/tests/recovery_flow.py

55 lines
3.8 KiB
Python
Raw Normal View History

2026-09-13 16:38:32 +00:00
"""Recovery choices keep the same task, goal history, plan and browser profile."""
2026-10-05 06:44:35 +00:00
import json,tempfile,threading,sys
2026-09-13 16:38:32 +00:00
from pathlib import Path
from http.server import ThreadingHTTPServer
from runtime_flow import Provider,Run,saved
2026-10-05 06:44:35 +00:00
from cli_flow import response,tool,paired
2026-09-13 16:38:32 +00:00
def main():
with tempfile.TemporaryDirectory(prefix='gb-recovery-') as root,ThreadingHTTPServer(('127.0.0.1',0),Provider) as server:
server.errors=[];threading.Thread(target=server.serve_forever,daemon=True).start();runs=[]
try:
2026-09-14 09:08:35 +00:00
replies=[response(tool('report_blocked',{'reason':'登入無法完成,兩次嘗試都停在驗證頁。','options':['由我在目前瀏覽器處理登入','先繼續寫文案']})),response(tool('external_write_file',{'path':'draft.txt','content':'draft without login'})),response(tool('report_done',{'message':'文案已備妥,登入仍未完成。'}))]
2026-09-13 16:38:32 +00:00
def callback(req):
if len(replies)==2:
2026-10-05 06:44:35 +00:00
paired(req['messages'])
assert any(m['role']=='user' and '先繼續寫文案' in m.get('content','') for m in req['messages']),req
assert any(m['role']=='user' and m.get('content')=='complete the original task' for m in req['messages']),req
2026-09-13 16:38:32 +00:00
return replies.pop(0)
server.callback=callback
2026-10-05 06:44:35 +00:00
run=Run(root,server);runs.append(run);run.wait_line('需要你的回覆');run.finish();before=saved(root);assert before['pending_question']['kind']=='recovery'
run=Run(root,server,['run','--session',before['id'],'b']);runs.append(run);run.finish();after=saved(root)
2026-09-13 16:38:32 +00:00
assert before['id']==after['id'] and not after['pending_question'];assert (Path(root)/'draft.txt').exists()
print('PASS blocker -> letter choice -> alternate work in same session',flush=True)
server.callback=lambda _:response(tool('report_blocked',{'reason':'Still blocked','options':['Try a different route']}))
2026-10-05 06:44:35 +00:00
run=Run(root,server);runs.append(run);run.wait_line('需要你的回覆');run.finish();pending=saved(root)
assert pending['pending_question']['options'][-1]=='停止這份工作'
requests=len(server.requests)
run=Run(root,server,['run','--session',pending['id'],'2']);runs.append(run);run.finish(code=130)
assert saved(root)['last_verdict']=='cancelled' and not saved(root)['pending_question']
assert len(server.requests)==requests
print('PASS explicit stop choice cancels without another model call',flush=True)
2026-09-13 16:38:32 +00:00
if '--browser' in sys.argv:
replies=[response(tool('browser_navigate',{'url':f'http://127.0.0.1:{server.server_port}/fixture'})),response(tool('browser_handoff',{'reason':'請在原頁面處理測試登入','options':['先寫文案']}))]
server.callback=lambda _:replies.pop(0)
2026-10-05 06:44:35 +00:00
run=Run(root,server);runs.append(run);run.wait_line('需要你的回覆');run.finish()
2026-09-13 16:38:32 +00:00
previous=saved(root);assert previous['pending_question']['kind']=='handoff'
replies=[response(tool('browser_read_page',{})),response(tool('report_done',{'message':'original browser restored and inspected'}))]
def restored(req):
2026-10-05 06:44:35 +00:00
if len(replies)==2:
assert any(m['role']=='user' and '我已經登入了' in m.get('content','') for m in req['messages']),req
assert not any('"login_verified":true' in m.get('content','') for m in req['messages']),req
else:
assert any(m['role']=='tool' and 'browser checkpoint evidence' in m.get('content','') for m in req['messages']),req
2026-09-13 16:38:32 +00:00
return replies.pop(0)
server.callback=restored
2026-10-05 06:44:35 +00:00
run=Run(root,server,['run','--session',previous['id'],'我已經登入了']);runs.append(run);run.finish()
2026-09-13 16:38:32 +00:00
assert saved(root)['id']==previous['id']
2026-10-05 06:44:35 +00:00
print('PASS parked handoff restores original browser for inspection; human reply is not proof of login',flush=True)
2026-09-13 16:38:32 +00:00
assert not server.errors,server.errors
finally:
for run in runs:
if run.process.poll() is None:run.process.terminate()
server.shutdown()
if __name__=='__main__':main()