@@ -79,6 +79,18 @@ function writeNormalizationCoreSource(root: string): string {
7979 return sourcePath ;
8080}
8181
82+ function addFakePluginSdkDistExport ( root : string , subpath : string ) : string {
83+ const packageJsonPath = path . join ( root , "package.json" ) ;
84+ const packageJson = JSON . parse ( fs . readFileSync ( packageJsonPath , "utf8" ) ) as {
85+ exports : Record < string , string > ;
86+ } ;
87+ const distPath = path . join ( root , "dist" , "plugin-sdk" , `${ subpath } .js` ) ;
88+ packageJson . exports [ `./plugin-sdk/${ subpath } ` ] = `./dist/plugin-sdk/${ subpath } .js` ;
89+ writeJsonFile ( packageJsonPath , packageJson ) ;
90+ fs . writeFileSync ( distPath , `export const ${ subpath . replaceAll ( "-" , "_" ) } = true;\n` , "utf8" ) ;
91+ return distPath ;
92+ }
93+
8294describe ( "installOpenClawPluginSdkNativeResolver" , ( ) => {
8395 it ( "resolves installed plugin SDK imports to the dev source root" , ( ) => {
8496 const stableRoot = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "openclaw-sdk-native-stable-" ) ) ;
@@ -111,6 +123,85 @@ describe("installOpenClawPluginSdkNativeResolver", () => {
111123 }
112124 } ) ;
113125
126+ it ( "resolves installed plugin SDK imports to an explicit dev source root" , ( ) => {
127+ const stableRoot = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "openclaw-sdk-native-stable-" ) ) ;
128+ const devRoot = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "openclaw-sdk-native-dev-source-" ) ) ;
129+ const { loaderModulePath } = writeFakeOpenClawPackage ( stableRoot ) ;
130+ writeFakeOpenClawPackage ( devRoot ) ;
131+ fs . mkdirSync ( path . join ( devRoot , "src" ) , { recursive : true } ) ;
132+ fs . mkdirSync ( path . join ( devRoot , "extensions" ) , { recursive : true } ) ;
133+ const externalPluginEntry = writeExternalPluginEntry ( path . join ( stableRoot , "external-plugin" ) ) ;
134+
135+ const installedAliases = installOpenClawPluginSdkNativeResolver ( {
136+ modulePath : loaderModulePath ,
137+ pluginModulePath : externalPluginEntry ,
138+ devSourceRoot : devRoot ,
139+ } ) ;
140+
141+ expect ( installedAliases ) . toContain ( "openclaw/plugin-sdk/agent-runtime" ) ;
142+ const requireFromPlugin = createRequire ( externalPluginEntry ) ;
143+ expect ( fs . realpathSync ( requireFromPlugin . resolve ( "openclaw/plugin-sdk/agent-runtime" ) ) ) . toBe (
144+ fs . realpathSync ( path . join ( devRoot , "dist" , "plugin-sdk" , "agent-runtime.js" ) ) ,
145+ ) ;
146+ } ) ;
147+
148+ it ( "updates native SDK aliases when the same plugin parent switches dev source roots" , ( ) => {
149+ const stableRoot = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "openclaw-sdk-native-stable-" ) ) ;
150+ const devRoot = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "openclaw-sdk-native-dev-source-" ) ) ;
151+ const { loaderModulePath } = writeFakeOpenClawPackage ( stableRoot ) ;
152+ writeFakeOpenClawPackage ( devRoot ) ;
153+ fs . mkdirSync ( path . join ( devRoot , "src" ) , { recursive : true } ) ;
154+ fs . mkdirSync ( path . join ( devRoot , "extensions" ) , { recursive : true } ) ;
155+ const externalPluginEntry = writeExternalPluginEntry ( path . join ( stableRoot , "external-plugin" ) ) ;
156+ const requireFromPlugin = createRequire ( externalPluginEntry ) ;
157+
158+ installOpenClawPluginSdkNativeResolver ( {
159+ modulePath : loaderModulePath ,
160+ pluginModulePath : externalPluginEntry ,
161+ } ) ;
162+ expect ( fs . realpathSync ( requireFromPlugin . resolve ( "openclaw/plugin-sdk/agent-runtime" ) ) ) . toBe (
163+ fs . realpathSync ( path . join ( stableRoot , "dist" , "plugin-sdk" , "agent-runtime.js" ) ) ,
164+ ) ;
165+
166+ installOpenClawPluginSdkNativeResolver ( {
167+ modulePath : loaderModulePath ,
168+ pluginModulePath : externalPluginEntry ,
169+ devSourceRoot : devRoot ,
170+ } ) ;
171+
172+ expect ( fs . realpathSync ( requireFromPlugin . resolve ( "openclaw/plugin-sdk/agent-runtime" ) ) ) . toBe (
173+ fs . realpathSync ( path . join ( devRoot , "dist" , "plugin-sdk" , "agent-runtime.js" ) ) ,
174+ ) ;
175+ } ) ;
176+
177+ it ( "removes stale native SDK aliases when a later dev root omits a subpath" , ( ) => {
178+ const stableRoot = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "openclaw-sdk-native-stable-" ) ) ;
179+ const devRoot = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "openclaw-sdk-native-dev-source-" ) ) ;
180+ const { loaderModulePath } = writeFakeOpenClawPackage ( stableRoot ) ;
181+ writeFakeOpenClawPackage ( devRoot ) ;
182+ const stableExtraPath = addFakePluginSdkDistExport ( stableRoot , "stable-extra" ) ;
183+ fs . mkdirSync ( path . join ( devRoot , "src" ) , { recursive : true } ) ;
184+ fs . mkdirSync ( path . join ( devRoot , "extensions" ) , { recursive : true } ) ;
185+ const externalPluginEntry = writeExternalPluginEntry ( path . join ( stableRoot , "external-plugin" ) ) ;
186+ const requireFromPlugin = createRequire ( externalPluginEntry ) ;
187+
188+ installOpenClawPluginSdkNativeResolver ( {
189+ modulePath : loaderModulePath ,
190+ pluginModulePath : externalPluginEntry ,
191+ } ) ;
192+ expect ( fs . realpathSync ( requireFromPlugin . resolve ( "openclaw/plugin-sdk/stable-extra" ) ) ) . toBe (
193+ fs . realpathSync ( stableExtraPath ) ,
194+ ) ;
195+
196+ installOpenClawPluginSdkNativeResolver ( {
197+ modulePath : loaderModulePath ,
198+ pluginModulePath : externalPluginEntry ,
199+ devSourceRoot : devRoot ,
200+ } ) ;
201+
202+ expect ( ( ) => requireFromPlugin . resolve ( "openclaw/plugin-sdk/stable-extra" ) ) . toThrow ( ) ;
203+ } ) ;
204+
114205 it ( "keeps native aliases on JS dist artifacts when source files exist" , ( ) => {
115206 const root = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "openclaw-sdk-native-source-resolver-" ) ) ;
116207 const { loaderModulePath } = writeFakeOpenClawPackage ( root ) ;
0 commit comments