22
33import argparse
44import atexit
5+ import contextlib
6+ import os
57import sys
8+ import threading
69import time
710from pathlib import Path
811
1619
1720tool_registry = ToolRegistry ()
1821
22+ # Session heartbeat: periodically persists session duration so that abnormal
23+ # termination (window close, Ctrl+C, SIGKILL) loses minimal data.
24+ _session_heartbeat_stop : threading .Event | None = None
25+
26+
27+ def _session_heartbeat (
28+ db ,
29+ session_id : int ,
30+ stop_event : threading .Event ,
31+ interval : float ,
32+ ) -> None :
33+ """Periodically persist session duration during normal operation."""
34+ while not stop_event .wait (interval ):
35+ with contextlib .suppress (Exception ):
36+ db .update_session_duration (session_id )
37+
1938
2039def init_workspace (start_path : str | None = None ) -> Workspace | None :
2140 """初始化工作区"""
@@ -42,12 +61,26 @@ def init_workspace(start_path: str | None = None) -> Workspace | None:
4261 tool_registry .set_session_id (session_id )
4362 workspace ._current_session_id = session_id
4463
64+ # Start a daemon heartbeat thread to periodically persist session duration
65+ global _session_heartbeat_stop
66+ _session_heartbeat_stop = threading .Event ()
67+ interval = int (os .getenv ("SESSION_UPDATE_INTERVAL" , "30" ))
68+ thread = threading .Thread (
69+ target = _session_heartbeat ,
70+ args = (workspace .db , session_id , _session_heartbeat_stop , interval ),
71+ daemon = True ,
72+ )
73+ thread .start ()
74+
4575 atexit .register (_cleanup , workspace , session_id )
4676
4777 return workspace
4878
4979
5080def _cleanup (workspace : Workspace , session_id : int ) -> None :
81+ global _session_heartbeat_stop
82+ if _session_heartbeat_stop is not None :
83+ _session_heartbeat_stop .set ()
5184 if session_id and hasattr (workspace , "db" ):
5285 workspace .db .close_session (session_id )
5386 workspace .db .close ()
0 commit comments