11import { WebContainer , type WebContainerProcess } from '@webcontainer/api' ;
22import type { TerminalManager } from './terminal.js' ;
3- import { buildWorkspaceFiles , buildContainerPackageJson , GIT_STUB_JS } from './workspace.js' ;
3+ import { buildWorkspaceFiles , buildContainerPackageJson , buildStubLoaderRegister , buildStubLoaderHooks , GIT_STUB_JS , OPENCLAW_START_SCRIPT , OPENCLAW_SETUP_SCRIPT } from './workspace.js' ;
44import { AuditLog , type AuditSource } from './audit.js' ;
55import { NETWORK_HOOK_CJS } from './network-hook.js' ;
66import { PolicyEngine , PolicyDeniedError , type PolicyAction } from './policy.js' ;
@@ -145,7 +145,13 @@ export class ContainerManager {
145145 }
146146
147147 /** Boot the WebContainer and mount all workspace files. */
148- async boot ( opts ?: { workspace ?: Record < string , string > ; services ?: Record < string , string > } ) : Promise < void > {
148+ async boot ( opts ?: {
149+ workspace ?: Record < string , string > ;
150+ services ?: Record < string , string > ;
151+ agentPackage ?: string ;
152+ agentVersion ?: string ;
153+ agentOverrides ?: Record < string , string > ;
154+ } ) : Promise < void > {
149155 this . setStatus ( 'booting' ) ;
150156 this . wc = await WebContainer . boot ( ) ;
151157
@@ -162,12 +168,28 @@ export class ContainerManager {
162168 for ( const fn of this . serverListeners ) fn ( port , url ) ;
163169 } ) ;
164170
165- await this . wc . mount ( {
166- 'package.json' : { file : { contents : buildContainerPackageJson ( opts ?. services ) } } ,
171+ const mountTree : Record < string , any > = {
172+ 'package.json' : { file : { contents : buildContainerPackageJson ( {
173+ agentPackage : opts ?. agentPackage ,
174+ agentVersion : opts ?. agentVersion ,
175+ extraDeps : opts ?. services ,
176+ extraOverrides : opts ?. agentOverrides ,
177+ } ) } } ,
167178 'git-stub.js' : { file : { contents : GIT_STUB_JS } } ,
168179 'network-hook.cjs' : { file : { contents : NETWORK_HOOK_CJS } } ,
169180 workspace : { directory : buildWorkspaceFiles ( opts ?. workspace ) } ,
170- } ) ;
181+ } ;
182+
183+ // Mount stub loader hooks when native deps are stubbed via overrides
184+ if ( opts ?. agentOverrides && Object . keys ( opts . agentOverrides ) . length > 0 ) {
185+ const pkgs = Object . keys ( opts . agentOverrides ) ;
186+ mountTree [ 'stub-loader.mjs' ] = { file : { contents : buildStubLoaderRegister ( pkgs ) } } ;
187+ mountTree [ 'stub-loader-hooks.mjs' ] = { file : { contents : buildStubLoaderHooks ( pkgs ) } } ;
188+ mountTree [ 'openclaw-start.mjs' ] = { file : { contents : OPENCLAW_START_SCRIPT } } ;
189+ mountTree [ 'openclaw-setup.cjs' ] = { file : { contents : OPENCLAW_SETUP_SCRIPT } } ;
190+ }
191+
192+ await this . wc . mount ( mountTree ) ;
171193
172194 this . audit ?. log ( 'boot.mount' , 'mounted workspace files' , {
173195 files : [ 'package.json' , 'git-stub.js' , 'network-hook.cjs' , 'workspace/' ] ,
@@ -573,14 +595,19 @@ export class ContainerManager {
573595 this . audit ?. log ( 'process.spawn' , spawnCmd , undefined , { source : 'agent' } ) ;
574596 this . activeProcessCount ++ ;
575597
598+ const nodeOptions = [ `--require ${ homeDir } /network-hook.cjs` ] ;
599+ if ( config . overrides && Object . keys ( config . overrides ) . length > 0 ) {
600+ nodeOptions . push ( `--import ${ homeDir } /stub-loader.mjs` ) ;
601+ }
602+
576603 this . shellProcess = await this . wc . spawn ( 'node' , [ entry , ...args ] , {
577604 terminal : { cols, rows } ,
578605 env : {
579606 ...this . apiEnvVars ,
580607 ...config . env ,
581608 PATH : `${ homeDir } /node_modules/.bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin` ,
582609 HOME : homeDir ,
583- NODE_OPTIONS : `--require ${ homeDir } /network-hook.cjs` ,
610+ NODE_OPTIONS : nodeOptions . join ( ' ' ) ,
584611 } ,
585612 } ) ;
586613
0 commit comments