@@ -185,7 +185,8 @@ def __init__(self) -> None:
185185 self ._lock = threading .RLock ()
186186 self ._db = SessionDbHolder ()
187187 self ._approval_requests : dict [str , queue .Queue [str ]] = {}
188- self ._gateway_approval_requests : dict [str , str ] = {}
188+ self ._approval_request_generations : dict [str , tuple [str , str ]] = {}
189+ self ._gateway_approval_requests : dict [str , tuple [str , str , str ]] = {}
189190 self ._gateway_approval_pattern_keys : dict [str , list [str ]] = {}
190191 self ._compression_requests : dict [str , queue .Queue [dict [str , Any ]]] = {}
191192 self ._background_notification_claims : dict [tuple [str , str ], dict [str , Any ]] = {}
@@ -1371,10 +1372,13 @@ def callback(command: str, description: str, *, allow_permanent: bool = True) ->
13711372 approval_id = uuid .uuid4 ().hex
13721373 response_queue : queue .Queue [str ] = queue .Queue (maxsize = 1 )
13731374 with self ._lock :
1375+ run_id = str (self ._sessions .get (session_id ).current_run_id or "" ) if self ._sessions .get (session_id ) else ""
13741376 self ._approval_requests [approval_id ] = response_queue
1377+ self ._approval_request_generations [approval_id ] = (session_id , run_id )
13751378 choices = ["once" , "session" , "always" , "deny" ] if allow_permanent else ["once" , "session" , "deny" ]
13761379 self ._append_event (session_id , {
13771380 "event" : "approval.requested" ,
1381+ "run_id" : run_id ,
13781382 "approval_id" : approval_id ,
13791383 "command" : str (command or "" ),
13801384 "description" : str (description or "" ),
@@ -1389,8 +1393,10 @@ def callback(command: str, description: str, *, allow_permanent: bool = True) ->
13891393 finally :
13901394 with self ._lock :
13911395 self ._approval_requests .pop (approval_id , None )
1396+ self ._approval_request_generations .pop (approval_id , None )
13921397 self ._append_event (session_id , {
13931398 "event" : "approval.resolved" ,
1399+ "run_id" : run_id ,
13941400 "approval_id" : approval_id ,
13951401 "choice" : choice ,
13961402 })
@@ -1467,11 +1473,14 @@ def callback(approval_data: dict[str, Any]) -> None:
14671473 approval_id = uuid .uuid4 ().hex
14681474 choices = ["once" , "session" , "always" , "deny" ]
14691475 pattern_keys = _approval_pattern_keys (approval_data )
1476+ request_id = str (approval_data .get ("request_id" ) or "" ).strip ()
14701477 with self ._lock :
1471- self ._gateway_approval_requests [approval_id ] = session_id
1478+ run_id = str (self ._sessions .get (session_id ).current_run_id or "" ) if self ._sessions .get (session_id ) else ""
1479+ self ._gateway_approval_requests [approval_id ] = (session_id , run_id , request_id )
14721480 self ._gateway_approval_pattern_keys [approval_id ] = pattern_keys
14731481 self ._append_event (session_id , {
14741482 "event" : "approval.requested" ,
1483+ "run_id" : run_id ,
14751484 "approval_id" : approval_id ,
14761485 "command" : str (approval_data .get ("command" ) or "" ),
14771486 "description" : str (approval_data .get ("description" ) or "" ),
@@ -2039,6 +2048,7 @@ def interrupt(self, session_id: str, message: str | None = None) -> dict[str, An
20392048 raise KeyError (f"unknown session: { session_id } " )
20402049 with session .lock :
20412050 self ._cancel_boundary_run (session )
2051+ interrupted_run_id = str (session .current_run_id or "" )
20422052 background_delegation_ids = self ._background_delegation_ids_for_session (session_id )
20432053 with self ._lock :
20442054 self ._suppressed_background_delegations .update (background_delegation_ids )
@@ -2050,6 +2060,7 @@ def interrupt(self, session_id: str, message: str | None = None) -> dict[str, An
20502060 if not hasattr (session .agent , "interrupt" ):
20512061 raise RuntimeError ("agent does not support interrupt" )
20522062 session .agent .interrupt (message )
2063+ self ._cancel_pending_approvals_for_generation (session_id , interrupted_run_id )
20532064 deadline = time .time () + 10.0
20542065 synced = False
20552066 while time .time () < deadline :
@@ -2066,6 +2077,50 @@ def interrupt(self, session_id: str, message: str | None = None) -> dict[str, An
20662077 "background_delegation_ids" : background_delegation_ids ,
20672078 }
20682079
2080+ def _cancel_pending_approvals_for_generation (self , session_id : str , run_id : str ) -> int :
2081+ if not session_id or not run_id :
2082+ return 0
2083+ terminal_queues : list [queue .Queue [str ]] = []
2084+ gateway_approvals : list [tuple [str , str , list [str ]]] = []
2085+ with self ._lock :
2086+ for approval_id , approval_generation in list (self ._approval_request_generations .items ()):
2087+ if approval_generation != (session_id , run_id ):
2088+ continue
2089+ response_queue = self ._approval_requests .pop (approval_id , None )
2090+ self ._approval_request_generations .pop (approval_id , None )
2091+ if response_queue is not None :
2092+ terminal_queues .append (response_queue )
2093+ for approval_id , approval_generation in list (self ._gateway_approval_requests .items ()):
2094+ if approval_generation [:2 ] != (session_id , run_id ):
2095+ continue
2096+ self ._gateway_approval_requests .pop (approval_id , None )
2097+ gateway_approvals .append ((
2098+ approval_id ,
2099+ approval_generation [2 ],
2100+ self ._gateway_approval_pattern_keys .pop (approval_id , []),
2101+ ))
2102+ for response_queue in terminal_queues :
2103+ try :
2104+ response_queue .put_nowait ("deny" )
2105+ except queue .Full :
2106+ pass
2107+ for approval_id , request_id , _pattern_keys in gateway_approvals :
2108+ try :
2109+ from tools .approval import resolve_gateway_approval
2110+
2111+ if request_id :
2112+ resolve_gateway_approval (session_id , "deny" , request_id = request_id )
2113+ except Exception :
2114+ pass
2115+ self ._append_event (session_id , {
2116+ "event" : "approval.resolved" ,
2117+ "run_id" : run_id ,
2118+ "approval_id" : approval_id ,
2119+ "choice" : "deny" ,
2120+ "reason" : "Session interrupted" ,
2121+ })
2122+ return len (terminal_queues ) + len (gateway_approvals )
2123+
20692124 def request_boundary_interrupt (
20702125 self ,
20712126 session_id : str ,
@@ -2165,31 +2220,39 @@ def respond_approval(self, approval_id: str, choice: str) -> dict[str, Any]:
21652220 if cleaned not in {"once" , "session" , "always" , "deny" }:
21662221 cleaned = "deny"
21672222 with self ._lock :
2168- response_queue = self ._approval_requests .get (approval_id )
2223+ response_queue = self ._approval_requests .pop (approval_id , None )
2224+ if response_queue is not None :
2225+ self ._approval_request_generations .pop (approval_id , None )
2226+ try :
2227+ response_queue .put_nowait (cleaned )
2228+ except queue .Full :
2229+ pass
21692230 if response_queue is None :
21702231 with self ._lock :
2171- gateway_session_id = self ._gateway_approval_requests .pop (approval_id , None )
2232+ gateway_generation = self ._gateway_approval_requests .pop (approval_id , None )
21722233 pattern_keys = self ._gateway_approval_pattern_keys .pop (approval_id , [])
2173- if gateway_session_id is None :
2234+ if gateway_generation is None :
21742235 return {"approval_id" : approval_id , "resolved" : False , "choice" : cleaned }
2236+ gateway_session_id , gateway_run_id , gateway_request_id = gateway_generation
21752237 try :
21762238 from tools .approval import resolve_gateway_approval
21772239
2178- resolved = resolve_gateway_approval (gateway_session_id , cleaned ) > 0
2240+ resolved = bool (gateway_request_id ) and resolve_gateway_approval (
2241+ gateway_session_id ,
2242+ cleaned ,
2243+ request_id = gateway_request_id ,
2244+ ) > 0
21792245 except Exception :
21802246 resolved = False
21812247 if resolved :
21822248 _persist_execute_code_approval_choice (gateway_session_id , pattern_keys , cleaned )
21832249 self ._append_event (gateway_session_id , {
21842250 "event" : "approval.resolved" ,
2251+ "run_id" : gateway_run_id ,
21852252 "approval_id" : approval_id ,
21862253 "choice" : cleaned ,
21872254 })
21882255 return {"approval_id" : approval_id , "resolved" : resolved , "choice" : cleaned }
2189- try :
2190- response_queue .put_nowait (cleaned )
2191- except queue .Full :
2192- pass
21932256 return {"approval_id" : approval_id , "resolved" : True , "choice" : cleaned }
21942257
21952258 def respond_clarify (self , clarify_id : str , response : str ) -> dict [str , Any ]:
0 commit comments