@@ -108,7 +108,7 @@ function expectResolvedBundledDirFromRoot(params: {
108108 expectResolvedBundledDir ( {
109109 cwd : params . cwd ?? params . repoRoot ,
110110 expectedDir : path . join ( params . repoRoot , params . expectedRelativeDir ) ,
111- ... ( params . argv1 ? { argv1 : params . argv1 } : { } ) ,
111+ argv1 : params . argv1 ?? path . join ( params . repoRoot , "openclaw.mjs" ) ,
112112 ...( params . bundledDirOverride ? { bundledDirOverride : params . bundledDirOverride } : { } ) ,
113113 ...( params . vitest !== undefined ? { vitest : params . vitest } : { } ) ,
114114 ...( params . execArgv ? { execArgv : params . execArgv } : { } ) ,
@@ -202,15 +202,15 @@ describe("resolveBundledPluginsDir", () => {
202202 } ,
203203 ] ,
204204 [
205- "prefers source extensions under vitest to avoid stale staged plugins " ,
205+ "does not prefer source extensions from VITEST alone " ,
206206 {
207207 prefix : "openclaw-bundled-dir-vitest-" ,
208208 hasExtensions : true ,
209209 hasDistRuntimeExtensions : true ,
210210 hasDistExtensions : true ,
211211 } ,
212212 {
213- expectedRelativeDir : " extensions",
213+ expectedRelativeDir : path . join ( "dist-runtime" , " extensions") ,
214214 vitest : "true" ,
215215 } ,
216216 ] ,
@@ -248,6 +248,8 @@ describe("resolveBundledPluginsDir", () => {
248248 seedBundledPluginTree ( repoRoot , path . join ( "dist-runtime" , "extensions" ) ) ;
249249 } else if ( expectation . expectedRelativeDir === path . join ( "dist" , "extensions" ) ) {
250250 seedBundledPluginTree ( repoRoot , path . join ( "dist" , "extensions" ) ) ;
251+ } else if ( expectation . expectedRelativeDir === "extensions" ) {
252+ seedBundledPluginTree ( repoRoot , "extensions" ) ;
251253 }
252254 expectResolvedBundledDirFromRoot ( {
253255 repoRoot,
@@ -270,6 +272,7 @@ describe("resolveBundledPluginsDir", () => {
270272 fs . mkdirSync ( path . join ( repoRoot , "dist-runtime" , "extensions" , "discord" ) , {
271273 recursive : true ,
272274 } ) ;
275+ seedBundledPluginTree ( repoRoot , "extensions" ) ;
273276
274277 expectResolvedBundledDirFromRoot ( {
275278 repoRoot,
@@ -296,6 +299,140 @@ describe("resolveBundledPluginsDir", () => {
296299 expect ( fs . readdirSync ( bundledDir ?? "" ) ) . toEqual ( [ ] ) ;
297300 } ) ;
298301
302+ it ( "ignores an existing override under an argv1-derived fake package root" , ( ) => {
303+ const installedRoot = createOpenClawRoot ( {
304+ prefix : "openclaw-bundled-dir-argv-override-reject-" ,
305+ hasDistExtensions : true ,
306+ } ) ;
307+ seedBundledPluginTree ( installedRoot , path . join ( "dist" , "extensions" ) ) ;
308+
309+ vi . spyOn ( process , "cwd" ) . mockReturnValue ( installedRoot ) ;
310+ process . argv [ 1 ] = path . join ( installedRoot , "openclaw.mjs" ) ;
311+ process . execArgv . length = 0 ;
312+ delete process . env . VITEST ;
313+ process . env . OPENCLAW_BUNDLED_PLUGINS_DIR = path . join ( installedRoot , "dist" , "extensions" ) ;
314+ delete process . env . OPENCLAW_DISABLE_BUNDLED_PLUGINS ;
315+
316+ const bundledDir = resolveBundledPluginsDir ( ) ;
317+
318+ expect ( bundledDir ) . toBeDefined ( ) ;
319+ expect ( fs . realpathSync ( bundledDir ! ) ) . not . toBe (
320+ fs . realpathSync ( path . join ( installedRoot , "dist" , "extensions" ) ) ,
321+ ) ;
322+ } ) ;
323+
324+ it ( "does not let VITEST relax existing override trust checks" , ( ) => {
325+ const overrideRoot = makeRepoRoot ( "openclaw-bundled-dir-vitest-override-reject-" ) ;
326+ seedBundledPluginTree ( overrideRoot , "extensions" , "memory-core" ) ;
327+
328+ vi . spyOn ( process , "cwd" ) . mockReturnValue ( overrideRoot ) ;
329+ process . argv [ 1 ] = "/usr/bin/env" ;
330+ process . execArgv . length = 0 ;
331+ process . env . VITEST = "true" ;
332+ process . env . OPENCLAW_BUNDLED_PLUGINS_DIR = path . join ( overrideRoot , "extensions" ) ;
333+ delete process . env . OPENCLAW_DISABLE_BUNDLED_PLUGINS ;
334+
335+ const bundledDir = resolveBundledPluginsDir ( ) ;
336+
337+ expect ( bundledDir ) . toBeDefined ( ) ;
338+ expect ( fs . realpathSync ( bundledDir ! ) ) . not . toBe (
339+ fs . realpathSync ( path . join ( overrideRoot , "extensions" ) ) ,
340+ ) ;
341+ } ) ;
342+
343+ it ( "does not let VITEST add cwd to bundled plugin resolution candidates" , ( ) => {
344+ const cwdRepoRoot = createOpenClawRoot ( {
345+ prefix : "openclaw-bundled-dir-vitest-cwd-" ,
346+ hasExtensions : true ,
347+ hasSrc : true ,
348+ hasGitCheckout : true ,
349+ } ) ;
350+ seedBundledPluginTree ( cwdRepoRoot , "extensions" , "memory-core" ) ;
351+
352+ vi . spyOn ( process , "cwd" ) . mockReturnValue ( cwdRepoRoot ) ;
353+ process . argv [ 1 ] = "/usr/bin/env" ;
354+ process . execArgv . length = 0 ;
355+ process . env . VITEST = "true" ;
356+ delete process . env . OPENCLAW_BUNDLED_PLUGINS_DIR ;
357+ delete process . env . OPENCLAW_DISABLE_BUNDLED_PLUGINS ;
358+
359+ const bundledDir = resolveBundledPluginsDir ( ) ;
360+
361+ expect ( bundledDir ) . toBeDefined ( ) ;
362+ expect ( fs . realpathSync ( bundledDir ! ) ) . not . toBe (
363+ fs . realpathSync ( path . join ( cwdRepoRoot , "extensions" ) ) ,
364+ ) ;
365+ } ) ;
366+
367+ it ( "falls back from a missing override instead of returning an untrusted future path" , ( ) => {
368+ vi . spyOn ( process , "cwd" ) . mockReturnValue ( makeRepoRoot ( "openclaw-bundled-dir-missing-cwd-" ) ) ;
369+ process . argv [ 1 ] = "/usr/bin/env" ;
370+ process . execArgv . length = 0 ;
371+ delete process . env . VITEST ;
372+ const missingOverride = path . join (
373+ makeRepoRoot ( "openclaw-bundled-dir-missing-override-" ) ,
374+ "extensions" ,
375+ ) ;
376+ process . env . OPENCLAW_BUNDLED_PLUGINS_DIR = missingOverride ;
377+ delete process . env . OPENCLAW_DISABLE_BUNDLED_PLUGINS ;
378+
379+ const bundledDir = resolveBundledPluginsDir ( ) ;
380+
381+ expect ( bundledDir ) . toBeDefined ( ) ;
382+ expect ( path . resolve ( bundledDir ! ) ) . not . toBe ( path . resolve ( missingOverride ) ) ;
383+ } ) ;
384+
385+ it ( "falls back to argv root when an existing rejected override is unrelated" , ( ) => {
386+ const installedRoot = createOpenClawRoot ( {
387+ prefix : "openclaw-bundled-dir-rejected-override-argv-" ,
388+ hasDistExtensions : true ,
389+ } ) ;
390+ seedBundledPluginTree ( installedRoot , path . join ( "dist" , "extensions" ) ) ;
391+ const overrideRoot = makeRepoRoot ( "openclaw-bundled-dir-rejected-override-" ) ;
392+ seedBundledPluginTree ( overrideRoot , "extensions" , "memory-core" ) ;
393+
394+ vi . spyOn ( process , "cwd" ) . mockReturnValue ( makeRepoRoot ( "openclaw-bundled-dir-rejected-cwd-" ) ) ;
395+ process . argv [ 1 ] = path . join ( installedRoot , "openclaw.mjs" ) ;
396+ process . execArgv . length = 0 ;
397+ delete process . env . VITEST ;
398+ process . env . OPENCLAW_BUNDLED_PLUGINS_DIR = path . join ( overrideRoot , "extensions" ) ;
399+ delete process . env . OPENCLAW_DISABLE_BUNDLED_PLUGINS ;
400+
401+ const bundledDir = resolveBundledPluginsDir ( ) ;
402+
403+ expect ( fs . realpathSync ( bundledDir ?? "" ) ) . toBe (
404+ fs . realpathSync ( path . join ( installedRoot , "dist" , "extensions" ) ) ,
405+ ) ;
406+ } ) ;
407+
408+ it ( "does not resolve bundled plugins from cwd when argv1 is not a package root" , ( ) => {
409+ const cwdRepoRoot = createOpenClawRoot ( {
410+ prefix : "openclaw-bundled-dir-untrusted-cwd-" ,
411+ hasExtensions : true ,
412+ hasSrc : true ,
413+ hasGitCheckout : true ,
414+ } ) ;
415+ fs . mkdirSync ( path . join ( cwdRepoRoot , "extensions" , "memory-core" ) , { recursive : true } ) ;
416+ fs . writeFileSync (
417+ path . join ( cwdRepoRoot , "extensions" , "memory-core" , "runtime-api.js" ) ,
418+ "export const marker = 'untrusted-cwd';\n" ,
419+ "utf8" ,
420+ ) ;
421+ vi . spyOn ( process , "cwd" ) . mockReturnValue ( cwdRepoRoot ) ;
422+ process . argv [ 1 ] = "/usr/bin/env" ;
423+ process . execArgv . length = 0 ;
424+ delete process . env . VITEST ;
425+ delete process . env . OPENCLAW_BUNDLED_PLUGINS_DIR ;
426+ delete process . env . OPENCLAW_DISABLE_BUNDLED_PLUGINS ;
427+
428+ const bundledDir = resolveBundledPluginsDir ( ) ;
429+
430+ expect ( bundledDir ) . toBeDefined ( ) ;
431+ expect ( fs . realpathSync ( bundledDir ! ) ) . not . toBe (
432+ fs . realpathSync ( path . join ( cwdRepoRoot , "extensions" ) ) ,
433+ ) ;
434+ } ) ;
435+
299436 it . each ( [
300437 {
301438 name : "prefers the running CLI package root over an unrelated cwd checkout" ,
0 commit comments