@@ -50,13 +50,18 @@ type OpenClawAcpxRuntimeOptions = AcpRuntimeOptions & {
5050 openclawWrapperRoot ?: string ;
5151 openclawGatewayInstanceId ?: string ;
5252 openclawProcessLeaseStore ?: AcpxProcessLeaseStore ;
53+ openclawToolsMcpBridgeEnabled ?: boolean ;
5354} ;
5455type AcpxRuntimeTestOptions = Record < string , unknown > & {
5556 openclawProcessCleanup ?: AcpxProcessCleanupDeps ;
5657} ;
5758type OpenClawRuntimeTurnInput = Parameters < NonNullable < AcpRuntime [ "startTurn" ] > > [ 0 ] ;
5859type OpenClawRuntimeEnsureInput = Parameters < AcpRuntime [ "ensureSession" ] > [ 0 ] ;
5960type AcpxDelegateEnsureInput = Parameters < BaseAcpxRuntime [ "ensureSession" ] > [ 0 ] ;
61+ type AcpxMcpServer = NonNullable < AcpRuntimeOptions [ "mcpServers" ] > [ number ] ;
62+
63+ const ACPX_OPENCLAW_TOOLS_MCP_SERVER_NAME = "openclaw-tools" ;
64+ const OPENCLAW_TOOLS_MCP_AGENT_SESSION_KEY_ENV = "OPENCLAW_TOOLS_MCP_AGENT_SESSION_KEY" ;
6065
6166type ResetAwareSessionStore = AcpSessionStore & {
6267 markFresh : ( sessionKey : string ) => void ;
@@ -682,6 +687,33 @@ function shouldUseDistinctBridgeDelegate(options: AcpRuntimeOptions): boolean {
682687 return Array . isArray ( mcpServers ) && mcpServers . length > 0 ;
683688}
684689
690+ function withOpenClawToolsMcpSessionEnv ( params : {
691+ enabled : boolean | undefined ;
692+ mcpServers : AcpRuntimeOptions [ "mcpServers" ] ;
693+ sessionKey : string ;
694+ } ) : AcpRuntimeOptions [ "mcpServers" ] {
695+ const sessionKey = params . sessionKey . trim ( ) ;
696+ if ( ! params . enabled || ! sessionKey || ! params . mcpServers ?. length ) {
697+ return params . mcpServers ;
698+ }
699+ let changed = false ;
700+ const nextServers = params . mcpServers . map ( ( server ) : AcpxMcpServer => {
701+ if ( server . name !== ACPX_OPENCLAW_TOOLS_MCP_SERVER_NAME || ! ( "command" in server ) ) {
702+ return server ;
703+ }
704+ changed = true ;
705+ const env = [
706+ ...server . env . filter ( ( entry ) => entry . name !== OPENCLAW_TOOLS_MCP_AGENT_SESSION_KEY_ENV ) ,
707+ {
708+ name : OPENCLAW_TOOLS_MCP_AGENT_SESSION_KEY_ENV ,
709+ value : sessionKey ,
710+ } ,
711+ ] ;
712+ return { ...server , env } ;
713+ } ) ;
714+ return changed ? nextServers : params . mcpServers ;
715+ }
716+
685717/** OpenClaw-managed ACP runtime implementation backed by the upstream acpx runtime. */
686718export class AcpxRuntime implements AcpRuntime {
687719 private readonly sessionStore : ResetAwareSessionStore ;
@@ -693,6 +725,10 @@ export class AcpxRuntime implements AcpRuntime {
693725 private readonly delegate : BaseAcpxRuntime ;
694726 private readonly bridgeSafeDelegate : BaseAcpxRuntime ;
695727 private readonly probeDelegate : BaseAcpxRuntime ;
728+ private readonly delegateOptions : AcpRuntimeOptions ;
729+ private readonly delegateTestOptions : BaseAcpxRuntimeTestOptions ;
730+ private readonly openclawToolsMcpBridgeEnabled : boolean ;
731+ private readonly openclawToolsSessionDelegates = new Map < string , BaseAcpxRuntime > ( ) ;
696732 private readonly processCleanupDeps : AcpxProcessCleanupDeps | undefined ;
697733 private readonly wrapperRoot : string | undefined ;
698734 private readonly gatewayInstanceId : string | undefined ;
@@ -706,6 +742,7 @@ export class AcpxRuntime implements AcpRuntime {
706742 this . wrapperRoot = options . openclawWrapperRoot ;
707743 this . gatewayInstanceId = options . openclawGatewayInstanceId ;
708744 this . processLeaseStore = options . openclawProcessLeaseStore ;
745+ this . openclawToolsMcpBridgeEnabled = options . openclawToolsMcpBridgeEnabled === true ;
709746 this . cwd = options . cwd ;
710747 this . sessionStore = createResetAwareSessionStore ( options . sessionStore , {
711748 gatewayInstanceId : this . gatewayInstanceId ,
@@ -723,20 +760,21 @@ export class AcpxRuntime implements AcpRuntime {
723760 sessionStore : this . sessionStore ,
724761 agentRegistry : this . scopedAgentRegistry ,
725762 } ;
726- this . delegate = new BaseAcpxRuntime (
727- sharedOptions ,
728- delegateTestOptions as BaseAcpxRuntimeTestOptions ,
729- ) ;
763+ this . delegateOptions = sharedOptions ;
764+ this . delegateTestOptions = delegateTestOptions as BaseAcpxRuntimeTestOptions ;
765+ this . delegate = new BaseAcpxRuntime ( sharedOptions , this . delegateTestOptions ) ;
730766 this . bridgeSafeDelegate = shouldUseDistinctBridgeDelegate ( options )
731767 ? new BaseAcpxRuntime (
732768 {
733769 ...sharedOptions ,
734770 mcpServers : [ ] ,
735771 } ,
736- delegateTestOptions as BaseAcpxRuntimeTestOptions ,
772+ this . delegateTestOptions ,
737773 )
738774 : this . delegate ;
739- this . probeDelegate = this . resolveDelegateForAgent ( resolveProbeAgentName ( options ) ) ;
775+ this . probeDelegate = this . openclawToolsMcpBridgeEnabled
776+ ? this . bridgeSafeDelegate
777+ : this . resolveDelegateForAgent ( resolveProbeAgentName ( options ) ) ;
740778 }
741779
742780 private resolveDelegateForAgent ( agentName : string | undefined ) : BaseAcpxRuntime {
@@ -751,6 +789,57 @@ export class AcpxRuntime implements AcpRuntime {
751789 return shouldUseBridgeSafeDelegateForCommand ( command ) ? this . bridgeSafeDelegate : this . delegate ;
752790 }
753791
792+ private resolveDelegateForSession ( params : {
793+ command : string | undefined ;
794+ sessionKey : string ;
795+ } ) : BaseAcpxRuntime {
796+ if ( shouldUseBridgeSafeDelegateForCommand ( params . command ) ) {
797+ return this . bridgeSafeDelegate ;
798+ }
799+ return this . resolveOpenClawToolsDelegateForSession ( params . sessionKey ) ;
800+ }
801+
802+ private resolveOpenClawToolsDelegateForSession ( sessionKey : string ) : BaseAcpxRuntime {
803+ if ( ! this . openclawToolsMcpBridgeEnabled ) {
804+ return this . delegate ;
805+ }
806+ const normalizedSessionKey = sessionKey . trim ( ) ;
807+ if ( ! normalizedSessionKey ) {
808+ return this . delegate ;
809+ }
810+ const cached = this . openclawToolsSessionDelegates . get ( normalizedSessionKey ) ;
811+ if ( cached ) {
812+ return cached ;
813+ }
814+ // Upstream acpx captures mcpServers at runtime construction. The managed
815+ // OpenClaw tools bridge needs per-session identity, so cache one delegate
816+ // per session with the scoped MCP env already embedded.
817+ const delegate = new BaseAcpxRuntime (
818+ {
819+ ...this . delegateOptions ,
820+ mcpServers : withOpenClawToolsMcpSessionEnv ( {
821+ enabled : this . openclawToolsMcpBridgeEnabled ,
822+ mcpServers : this . delegateOptions . mcpServers ,
823+ sessionKey : normalizedSessionKey ,
824+ } ) ,
825+ } ,
826+ this . delegateTestOptions ,
827+ ) ;
828+ this . openclawToolsSessionDelegates . set ( normalizedSessionKey , delegate ) ;
829+ return delegate ;
830+ }
831+
832+ private releaseOpenClawToolsDelegateForSession ( sessionKey : string ) : void {
833+ if ( ! this . openclawToolsMcpBridgeEnabled ) {
834+ return ;
835+ }
836+ const normalizedSessionKey = sessionKey . trim ( ) ;
837+ if ( ! normalizedSessionKey ) {
838+ return ;
839+ }
840+ this . openclawToolsSessionDelegates . delete ( normalizedSessionKey ) ;
841+ }
842+
754843 private async resolveDelegateForHandle ( handle : AcpRuntimeHandle ) : Promise < BaseAcpxRuntime > {
755844 const record = await this . sessionStore . load ( handle . acpxRecordId ?? handle . sessionKey ) ;
756845 return this . resolveDelegateForLoadedRecord ( handle , record ) ;
@@ -762,9 +851,17 @@ export class AcpxRuntime implements AcpRuntime {
762851 ) : BaseAcpxRuntime {
763852 const recordCommand = readAgentCommandFromRecord ( record ) ;
764853 if ( recordCommand ) {
765- return this . resolveDelegateForCommand ( recordCommand ) ;
854+ return this . resolveDelegateForSession ( {
855+ command : recordCommand ,
856+ sessionKey : handle . sessionKey ,
857+ } ) ;
766858 }
767- return this . resolveDelegateForAgent ( readAgentFromHandle ( handle ) ) ;
859+ const agentName = readAgentFromHandle ( handle ) ;
860+ const command = resolveAgentCommandForName ( {
861+ agentName,
862+ agentRegistry : this . agentRegistry ,
863+ } ) ;
864+ return this . resolveDelegateForSession ( { command, sessionKey : handle . sessionKey } ) ;
768865 }
769866
770867 private async resolveCommandForHandle ( handle : AcpRuntimeHandle ) : Promise < string | undefined > {
@@ -980,7 +1077,7 @@ export class AcpxRuntime implements AcpRuntime {
9801077 agentName : input . agent ,
9811078 agentRegistry : this . agentRegistry ,
9821079 } ) ;
983- const delegate = this . resolveDelegateForCommand ( command ) ;
1080+ const delegate = this . resolveDelegateForSession ( { command, sessionKey : input . sessionKey } ) ;
9841081 const claudeModelOverride = isClaudeAcpCommand ( command )
9851082 ? normalizeClaudeAcpModelOverride ( input . model )
9861083 : undefined ;
@@ -1264,6 +1361,9 @@ export class AcpxRuntime implements AcpRuntime {
12641361 }
12651362
12661363 async prepareFreshSession ( input : { sessionKey : string } ) : Promise < void > {
1364+ // Fresh reset has no ACP handle to close the delegate's upstream client.
1365+ // Keep the scoped delegate reachable so the next ensure can replace it;
1366+ // close() owns cache release when the session lifecycle ends.
12671367 this . sessionStore . markFresh ( input . sessionKey ) ;
12681368 }
12691369
@@ -1272,8 +1372,9 @@ export class AcpxRuntime implements AcpRuntime {
12721372 input . handle . acpxRecordId ?? input . handle . sessionKey ,
12731373 ) ;
12741374 let closeSucceeded ;
1375+ const delegate = this . resolveDelegateForLoadedRecord ( input . handle , record ) ;
12751376 try {
1276- await this . resolveDelegateForLoadedRecord ( input . handle , record ) . close ( {
1377+ await delegate . close ( {
12771378 handle : input . handle ,
12781379 reason : input . reason ,
12791380 discardPersistentState : input . discardPersistentState ,
@@ -1282,6 +1383,9 @@ export class AcpxRuntime implements AcpRuntime {
12821383 } finally {
12831384 await this . cleanupProcessTreeForRecord ( input . handle , record ) ;
12841385 }
1386+ if ( closeSucceeded ) {
1387+ this . releaseOpenClawToolsDelegateForSession ( input . handle . sessionKey ) ;
1388+ }
12851389 if ( closeSucceeded && input . discardPersistentState ) {
12861390 this . sessionStore . markFresh ( input . handle . sessionKey ) ;
12871391 }
0 commit comments