@@ -74,6 +74,32 @@ type LockableFunction = ((...args: unknown[]) => unknown) & {
7474 __openclawSessionWriteLockInstalled ?: boolean ;
7575} ;
7676
77+ type SessionEventHookContext = {
78+ session : unknown ;
79+ active : boolean ;
80+ } ;
81+
82+ const sessionEventHookContext = new AsyncLocalStorage < SessionEventHookContext > ( ) ;
83+
84+ function isProcessingAgentEventInCurrentChain ( session : unknown ) : boolean {
85+ const context = sessionEventHookContext . getStore ( ) ;
86+ return context ?. active === true && context . session === session ;
87+ }
88+
89+ async function withProcessingAgentEvent < T > (
90+ session : unknown ,
91+ run : ( ) => Promise < T > | T ,
92+ ) : Promise < T > {
93+ const context : SessionEventHookContext = { session, active : true } ;
94+ return await sessionEventHookContext . run ( context , async ( ) => {
95+ try {
96+ return await run ( ) ;
97+ } finally {
98+ context . active = false ;
99+ }
100+ } ) ;
101+ }
102+
77103function sessionHasExtensionHandlers ( session : SessionEventProcessor , eventType : string ) : boolean {
78104 const extensionRunner = session [ "_extensionRunner" ] ;
79105 const hasHandlers = extensionRunner ?. hasHandlers ;
@@ -662,6 +688,15 @@ async function waitForSessionEventQueue(session: unknown): Promise<void> {
662688 }
663689}
664690
691+ async function waitForSessionEventQueueBeforeHook ( session : unknown ) : Promise < void > {
692+ // Hook calls made by _handleAgentEvent are already inside the current queue
693+ // entry. Draining there waits on itself; detached/external hook work still drains.
694+ if ( isProcessingAgentEventInCurrentChain ( session ) ) {
695+ return ;
696+ }
697+ await waitForSessionEventQueue ( session ) ;
698+ }
699+
665700function installAwaitableSessionEventQueue ( session : unknown ) : void {
666701 const owner = session as SessionEventQueueBridge ;
667702 const original = owner [ "_handleAgentEvent" ] ;
@@ -731,10 +766,14 @@ export function installSessionEventWriteLock(params: {
731766 event : unknown ,
732767 signal ?: unknown ,
733768 ) {
734- if ( ! eventMayReachTranscriptWriters ( session , event ) ) {
735- return await original . call ( this , event , signal ) ;
736- }
737- return await params . withSessionWriteLock ( async ( ) => await original . call ( this , event , signal ) ) ;
769+ return await withProcessingAgentEvent ( session , async ( ) => {
770+ if ( ! eventMayReachTranscriptWriters ( session , event ) ) {
771+ return await original . call ( this , event , signal ) ;
772+ }
773+ return await params . withSessionWriteLock (
774+ async ( ) => await original . call ( this , event , signal ) ,
775+ ) ;
776+ } ) ;
738777 } ;
739778 wrapped [ "__openclawSessionEventQueueAwaitInstalled" ] =
740779 original [ "__openclawSessionEventQueueAwaitInstalled" ] ;
@@ -756,28 +795,28 @@ export function installSessionExternalHookWriteLock(params: {
756795 owner : agent as Record < string , unknown > ,
757796 key : "beforeToolCall" ,
758797 shouldLock : ( ) => true ,
759- waitBeforeLock : ( ) => waitForSessionEventQueue ( session ) ,
798+ waitBeforeLock : ( ) => waitForSessionEventQueueBeforeHook ( session ) ,
760799 withSessionWriteLock : params . withSessionWriteLock ,
761800 } ) ;
762801 installLockableFunction ( {
763802 owner : agent as Record < string , unknown > ,
764803 key : "afterToolCall" ,
765804 shouldLock : ( ) => sessionHasExtensionHandlers ( session , "tool_result" ) ,
766- waitBeforeLock : ( ) => waitForSessionEventQueue ( session ) ,
805+ waitBeforeLock : ( ) => waitForSessionEventQueueBeforeHook ( session ) ,
767806 withSessionWriteLock : params . withSessionWriteLock ,
768807 } ) ;
769808 installLockableFunction ( {
770809 owner : agent as Record < string , unknown > ,
771810 key : "onPayload" ,
772811 shouldLock : ( ) => sessionHasExtensionHandlers ( session , "before_provider_request" ) ,
773- waitBeforeLock : ( ) => waitForSessionEventQueue ( session ) ,
812+ waitBeforeLock : ( ) => waitForSessionEventQueueBeforeHook ( session ) ,
774813 withSessionWriteLock : params . withSessionWriteLock ,
775814 } ) ;
776815 installLockableFunction ( {
777816 owner : agent as Record < string , unknown > ,
778817 key : "onResponse" ,
779818 shouldLock : ( ) => sessionHasExtensionHandlers ( session , "after_provider_response" ) ,
780- waitBeforeLock : ( ) => waitForSessionEventQueue ( session ) ,
819+ waitBeforeLock : ( ) => waitForSessionEventQueueBeforeHook ( session ) ,
781820 withSessionWriteLock : params . withSessionWriteLock ,
782821 } ) ;
783822 }
0 commit comments