2026-09-13 16:38:32 +00:00
""" Recovery choices keep the same task, goal history, plan and browser profile. """
import json , tempfile , threading , signal , sys
from pathlib import Path
from http . server import ThreadingHTTPServer
from runtime_flow import Provider , Run , saved
from cli_flow import response , tool
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 :
last = json . loads ( req [ ' messages ' ] [ - 1 ] [ ' content ' ] ) ; assert last [ ' status ' ] == ' replan ' and last [ ' answer ' ] == ' 先繼續寫文案 ' , last
return replies . pop ( 0 )
server . callback = callback
run = Run ( root , server ) ; runs . append ( run ) ; run . wait_line ( ' 需要你的回覆 ' ) ; before = json . loads ( max ( Path ( root , ' sessions ' ) . glob ( ' *.json ' ) , key = lambda p : p . stat ( ) . st_mtime_ns ) . read_text ( ) ) ; assert before [ ' pending_question ' ] [ ' kind ' ] == ' recovery '
run . send ( ' b ' ) ; run . finish ( ) ; after = saved ( root )
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 ' ] } ) )
run = Run ( root , server ) ; runs . append ( run ) ; run . wait_line ( ' 需要你的回覆 ' ) ; run . send ( ' 2 ' ) ; run . finish ( code = 1 )
assert saved ( root ) [ ' last_verdict ' ] == ' blocked '
print ( ' PASS explicit stop choice ends with blocked verdict ' , flush = True )
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 )
run = Run ( root , server ) ; runs . append ( run ) ; run . wait_line ( ' 需要你的回覆 ' ) ; run . process . send_signal ( signal . SIGINT ) ; run . finish ( code = 130 )
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 ) :
assert any ( ' login_verified ' in m . get ( ' content ' , ' ' ) for m in req [ ' messages ' ] ) , req
return replies . pop ( 0 )
server . callback = restored
run = Run ( root , server , [ ' run ' , ' --session ' , previous [ ' id ' ] , ' continue ' ] ) ; runs . append ( run ) ; run . wait_line ( ' 需要你的回覆 ' ) ; run . send ( ' 我已經登入了 ' ) ; run . finish ( )
assert saved ( root ) [ ' id ' ] == previous [ ' id ' ]
print ( ' PASS interrupted handoff reopens original browser before asking; returned control is not assumed login success ' , flush = True )
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 ( )