@@ -67,22 +67,32 @@ def _load_sessions():
6767 with open (path , "r" ) as f :
6868 return json .load (f )
6969
70- def _save_session (session_id , project_uuid = "" , status = "active" , text = "" ):
70+ def _save_session (session_id , project_uuid = "" , status = "active" , text = "" ,
71+ result_urls = None , completed_at = "" ):
7172 sessions = _load_sessions ()
7273 for s in sessions :
7374 if s ["session_id" ] == session_id :
7475 s ["status" ] = status
7576 if project_uuid :
7677 s ["project_uuid" ] = project_uuid
78+ if result_urls :
79+ s ["result_urls" ] = result_urls
80+ if completed_at :
81+ s ["completed_at" ] = completed_at
7782 break
7883 else :
79- sessions . append ( {
84+ entry = {
8085 "session_id" : session_id ,
8186 "project_uuid" : project_uuid ,
8287 "status" : status ,
8388 "text" : text [:80 ],
8489 "created_at" : datetime .now ().isoformat (),
85- })
90+ }
91+ if result_urls :
92+ entry ["result_urls" ] = result_urls
93+ if completed_at :
94+ entry ["completed_at" ] = completed_at
95+ sessions .append (entry )
8696 sessions = sessions [- 50 :]
8797 path = _sessions_file_path ()
8898 os .makedirs (os .path .dirname (path ), exist_ok = True )
@@ -396,6 +406,11 @@ def cmd_query_session(args):
396406 # Extract result URLs
397407 urls = extract_result_urls (messages )
398408
409+ # Persist results to local so recover can read without API
410+ if urls :
411+ _save_session (session_id , project_uuid = args .project_id or "" , status = "completed" ,
412+ result_urls = urls , completed_at = datetime .now ().isoformat ())
413+
399414 out = {"messages" : messages }
400415 if args .project_id :
401416 out ["projectUrl" ] = f"{ PROJECT_CANVAS_BASE } { args .project_id } "
@@ -463,7 +478,8 @@ def cmd_wait_and_deliver(args):
463478 urls = extract_result_urls (messages )
464479
465480 if urls :
466- _save_session (session_id , project_uuid = project_uuid , status = "completed" )
481+ _save_session (session_id , project_uuid = project_uuid , status = "completed" ,
482+ result_urls = urls , completed_at = datetime .now ().isoformat ())
467483 deliver_results (urls , project_uuid = project_uuid )
468484
469485 # Auto-download
@@ -496,12 +512,58 @@ def cmd_wait_and_deliver(args):
496512
497513
498514def cmd_recover (args ):
499- pending = _get_pending_sessions ()
515+ sessions = _load_sessions ()
516+ if not sessions :
517+ print ("No sessions found." )
518+ return
519+
520+ # Show completed sessions from local (no API call needed)
521+ completed = [s for s in sessions if s ["status" ] in ("completed" , "failed" , "timeout" )]
522+ pending = [s for s in sessions if s ["status" ] not in ("completed" , "failed" , "timeout" )]
523+
524+ if completed :
525+ print (f"π { len (completed )} completed session(s) (from local):" )
526+ for s in completed [- 10 :]:
527+ sid = s ["session_id" ]
528+ project_uuid = s .get ("project_uuid" , "" )
529+ local_urls = s .get ("result_urls" , [])
530+ status = s ["status" ]
531+ text = s .get ("text" , "" )
532+
533+ if status == "completed" and local_urls :
534+ print (f" β
{ sid [:16 ]} ... Completed ({ text } )" )
535+ for url in local_urls :
536+ print (f" π¬ { url } " )
537+ if project_uuid :
538+ print (f" π¨ Canvas: { PROJECT_CANVAS_BASE } { project_uuid } " )
539+ elif status == "failed" :
540+ print (f" β { sid [:16 ]} ... Failed ({ text } )" )
541+ elif status == "timeout" :
542+ print (f" β° { sid [:16 ]} ... Timeout ({ text } )" )
543+ else :
544+ # Completed but no local URLs β re-fetch once
545+ try :
546+ result = call_gateway ("GET" , f"/libtv/v1/session/{ sid } " )
547+ messages = result .get ("messages" ) or []
548+ urls = extract_result_urls (messages )
549+ if urls :
550+ _save_session (sid , project_uuid = project_uuid , status = "completed" ,
551+ result_urls = urls , completed_at = datetime .now ().isoformat ())
552+ print (f" β
{ sid [:16 ]} ... Completed ({ text } )" )
553+ for url in urls :
554+ print (f" π¬ { url } " )
555+ else :
556+ print (f" β
{ sid [:16 ]} ... Completed, no result URLs ({ text } )" )
557+ except Exception as e :
558+ print (f" β
{ sid [:16 ]} ... Completed, fetch error: { e } " )
559+
500560 if not pending :
501- print ("No pending sessions found." )
561+ if not completed :
562+ print ("No sessions found." )
502563 return
503564
504- print (f"Found { len (pending )} pending session(s):" )
565+ # Check pending sessions via API
566+ print (f"\n π { len (pending )} pending session(s) (checking API...):" )
505567 for s in pending :
506568 sid = s ["session_id" ]
507569 project_uuid = s .get ("project_uuid" , "" )
@@ -513,7 +575,8 @@ def cmd_recover(args):
513575 urls = extract_result_urls (messages )
514576
515577 if urls :
516- _save_session (sid , project_uuid = project_uuid , status = "completed" )
578+ _save_session (sid , project_uuid = project_uuid , status = "completed" ,
579+ result_urls = urls , completed_at = datetime .now ().isoformat ())
517580 print (f" β
{ sid [:16 ]} ... Completed!" )
518581 for url in urls :
519582 print (f" π¬ { url } " )
@@ -544,6 +607,11 @@ def cmd_tasks(args):
544607 video_url = t .get ("video_url" )
545608 error = t .get ("error_message" )
546609
610+ # Persist completed task results to local for offline recovery
611+ if status == "completed" and video_url :
612+ _save_session (tid , status = "completed" ,
613+ result_urls = [video_url ], completed_at = completed or "" )
614+
547615 status_icon = {
548616 "completed" : "β
" , "failed" : "β" , "composing" : "β³" ,
549617 "rendering" : "β³" , "pending" : "π" ,
0 commit comments