@@ -1101,6 +1101,9 @@ def _send(self, frame):
11011101 _log_failure ("Execution progress delivery stopped." , self .record , exc )
11021102
11031103 def _progress (self , event ):
1104+ if type (event ) is dict and event .get ("type" ) == "m365_pending_action_reference" :
1105+ self ._pending_action_created (event )
1106+ return
11041107 if type (event ) is not dict or event .get ("type" ) != "step" :
11051108 return
11061109 extra = {
@@ -1118,6 +1121,72 @@ def _progress(self, event):
11181121 event .get ("step_index" ), event .get ("capability_id" ), ** extra ,
11191122 ))
11201123
1124+ def _pending_action_created (self , event ):
1125+ # The producer runs inside a separate request. Only its exact step-owned
1126+ # reference crosses the bridge; tool/model snapshots are never published.
1127+ from functions_orchestration_m365 import step_request_id
1128+
1129+ reference = event .get ("reference" )
1130+ step_id = event .get ("step_id" )
1131+ root = self .record .get ("attempt_root_run_id" ) or self .record ["id" ]
1132+ if (
1133+ type (reference ) is not dict
1134+ or event .get ("run_id" ) != self .record ["id" ]
1135+ or reference .get ("user_id" ) != self .record ["user_id" ]
1136+ or reference .get ("conversation_id" ) != self .record ["conversation_id" ]
1137+ or not any (
1138+ step .get ("step_id" ) == step_id for step in self .record ["plan" ]["steps" ]
1139+ )
1140+ or reference .get ("request_id" ) != step_request_id (f"{ root } \x00 { step_id } " )
1141+ or not isinstance (reference .get ("id" ), str ) or not reference ["id" ]
1142+ ):
1143+ return
1144+ with self .lease .lock :
1145+ current = self .lease .read ()
1146+ action_ids = list (dict .fromkeys ([
1147+ * (current .get ("m365_pending_action_ids" ) or []), reference ["id" ],
1148+ ]))
1149+ self .lease .update ({"m365_pending_action_ids" : action_ids })
1150+ try :
1151+ from functions_msgraph_pending_actions import get_chat_pending_action_cards
1152+
1153+ cards = get_chat_pending_action_cards (
1154+ self .record ["user_id" ], self .record ["conversation_id" ],
1155+ request_id = reference ["request_id" ], action_ids = [reference ["id" ]],
1156+ )
1157+ for card in cards :
1158+ self ._send (serialize_sse ({
1159+ "type" : "m365_pending_action" , "pending_action" : card ,
1160+ "conversation_id" : self .record ["conversation_id" ],
1161+ "run_id" : self .record ["id" ], "step_id" : step_id ,
1162+ "user_message_id" : self .record .get ("user_message_id" ),
1163+ "request_id" : reference ["request_id" ],
1164+ }))
1165+ except Exception as error :
1166+ _log_failure ("A saved outgoing action could not be displayed live." , self .record , error )
1167+
1168+ def _pending_action_payload (self ):
1169+ try :
1170+ record = self ._final_record or self .lease .read ()
1171+ action_ids = record .get ("m365_pending_action_ids" ) or []
1172+ if not action_ids :
1173+ return {}
1174+ from functions_msgraph_pending_actions import get_chat_pending_action_cards
1175+
1176+ cards = []
1177+ for offset in range (0 , len (action_ids ), 100 ):
1178+ cards .extend (get_chat_pending_action_cards (
1179+ self .record ["user_id" ], self .record ["conversation_id" ],
1180+ action_ids = action_ids [offset :offset + 100 ],
1181+ ))
1182+ return {"m365_pending_actions" : cards }
1183+ except Exception as error :
1184+ _log_failure ("Saved outgoing actions could not be refreshed." , self .record , error )
1185+ return {"m365_pending_actions_error" : {
1186+ "error" : "m365_pending_actions_unavailable" ,
1187+ "message" : "Outgoing actions could not be refreshed. Reload them; do not repeat the request." ,
1188+ }}
1189+
11211190 def _persist (self , record_type , payload ):
11221191 if record_type == "run" :
11231192 try :
@@ -1571,6 +1640,8 @@ def _finalize(self, result, error, *, refreshes=0):
15711640 if workflow_result_reads and workflow_result_contexts else {}
15721641 ),
15731642 }
1643+ if current .get ("m365_pending_action_ids" ):
1644+ metadata ["m365_pending_action_ids" ] = list (current ["m365_pending_action_ids" ])
15741645 thread = self ._regeneration_thread ()
15751646 if thread :
15761647 metadata ["thread_info" ] = thread
@@ -1659,6 +1730,7 @@ def _finalize(self, result, error, *, refreshes=0):
16591730 payload .update ({
16601731 "metadata" : document .get ("metadata" ) or {}, "role" : document ["role" ],
16611732 "replace_content" : True , "blocked" : blocked ,
1733+ ** self ._pending_action_payload (),
16621734 })
16631735 return [build_content_event (answer ), serialize_sse (strip_private_chat_checks (payload ))]
16641736
@@ -1690,6 +1762,9 @@ def _finish(self, result, error):
16901762 finalization_status = "failed" , outputs = [],
16911763 ),
16921764 ]
1765+ terminal = json .loads (self ._frames [- 1 ].partition ("data:" )[2 ].strip ())
1766+ terminal .update (self ._pending_action_payload ())
1767+ self ._frames [- 1 ] = serialize_sse (terminal )
16931768 if self .record .get ("requires_fresh_review" ):
16941769 status = (self ._final_record or {}).get ("status" ) or "failed"
16951770 state = {"completed" : "completed" , "failed" : "failed" , "cancelled" : "interrupted" }.get (status )
0 commit comments