@@ -1902,7 +1902,7 @@ describe("updateNpmInstalledPlugins", () => {
19021902 } ) ;
19031903 } ) ;
19041904
1905- it ( "falls through to npm reinstall when metadata probing fails" , async ( ) => {
1905+ it ( "falls through to npm reinstall when metadata probing fails for valid specs " , async ( ) => {
19061906 const warn = vi . fn ( ) ;
19071907 const installPath = createInstalledPackageDir ( {
19081908 name : "@martian-engineering/lossless-claw" ,
@@ -1937,6 +1937,107 @@ describe("updateNpmInstalledPlugins", () => {
19371937 expect ( installPluginFromNpmSpecMock ) . toHaveBeenCalledTimes ( 1 ) ;
19381938 } ) ;
19391939
1940+ it ( "records range metadata probing failures without falling through to npm reinstall" , async ( ) => {
1941+ const warn = vi . fn ( ) ;
1942+ const installPath = createInstalledPackageDir ( {
1943+ name : "@martian-engineering/lossless-claw" ,
1944+ version : "0.9.0" ,
1945+ } ) ;
1946+ runCommandWithTimeoutMock . mockResolvedValueOnce ( {
1947+ code : 1 ,
1948+ stdout : "" ,
1949+ stderr : "registry timeout" ,
1950+ } ) ;
1951+ const result = await updateNpmInstalledPlugins ( {
1952+ config : createNpmInstallConfig ( {
1953+ pluginId : "lossless-claw" ,
1954+ spec : "@martian-engineering/lossless-claw@^0.9.0" ,
1955+ installPath,
1956+ } ) ,
1957+ pluginIds : [ "lossless-claw" ] ,
1958+ logger : { warn } ,
1959+ } ) ;
1960+
1961+ expect ( warn ) . not . toHaveBeenCalled ( ) ;
1962+ expect ( installPluginFromNpmSpecMock ) . not . toHaveBeenCalled ( ) ;
1963+ expect ( result . changed ) . toBe ( false ) ;
1964+ expect ( result . outcomes ) . toEqual ( [
1965+ {
1966+ pluginId : "lossless-claw" ,
1967+ status : "error" ,
1968+ message : "Failed to check lossless-claw: npm view failed: registry timeout" ,
1969+ } ,
1970+ ] ) ;
1971+ } ) ;
1972+
1973+ it ( "uses failure cleanup when metadata probing fails and disableOnFailure is enabled" , async ( ) => {
1974+ const warn = vi . fn ( ) ;
1975+ const installPath = createInstalledPackageDir ( {
1976+ name : "@martian-engineering/lossless-claw" ,
1977+ version : "0.9.0" ,
1978+ } ) ;
1979+ runCommandWithTimeoutMock . mockResolvedValueOnce ( {
1980+ code : 1 ,
1981+ stdout : "" ,
1982+ stderr : "registry timeout" ,
1983+ } ) ;
1984+
1985+ const result = await updateNpmInstalledPlugins ( {
1986+ config : {
1987+ plugins : {
1988+ allow : [ "lossless-claw" , "keep" ] ,
1989+ deny : [ "lossless-claw" , "blocked" ] ,
1990+ slots : {
1991+ memory : "lossless-claw" ,
1992+ contextEngine : "lossless-claw" ,
1993+ } ,
1994+ entries : {
1995+ "lossless-claw" : {
1996+ enabled : true ,
1997+ config : { preserved : true } ,
1998+ } ,
1999+ } ,
2000+ installs : {
2001+ "lossless-claw" : {
2002+ source : "npm" ,
2003+ spec : "@martian-engineering/lossless-claw@^0.9.0" ,
2004+ installPath,
2005+ resolvedName : "@martian-engineering/lossless-claw" ,
2006+ resolvedVersion : "0.9.0" ,
2007+ resolvedSpec :
"@martian-engineering/[email protected] " , 2008+ } ,
2009+ } ,
2010+ } ,
2011+ } ,
2012+ pluginIds : [ "lossless-claw" ] ,
2013+ disableOnFailure : true ,
2014+ logger : { warn } ,
2015+ } ) ;
2016+
2017+ const message =
2018+ 'Disabled "lossless-claw" after plugin update failure; OpenClaw will continue without it. Failed to check lossless-claw: npm view failed: registry timeout' ;
2019+ expect ( warn ) . toHaveBeenCalledWith ( message ) ;
2020+ expect ( installPluginFromNpmSpecMock ) . not . toHaveBeenCalled ( ) ;
2021+ expect ( result . changed ) . toBe ( true ) ;
2022+ expect ( result . config . plugins ?. entries ?. [ "lossless-claw" ] ) . toEqual ( {
2023+ enabled : false ,
2024+ config : { preserved : true } ,
2025+ } ) ;
2026+ expect ( result . config . plugins ?. allow ) . toEqual ( [ "keep" ] ) ;
2027+ expect ( result . config . plugins ?. deny ) . toEqual ( [ "blocked" ] ) ;
2028+ expect ( result . config . plugins ?. slots ) . toEqual ( {
2029+ memory : "memory-core" ,
2030+ contextEngine : "legacy" ,
2031+ } ) ;
2032+ expect ( result . outcomes ) . toEqual ( [
2033+ {
2034+ pluginId : "lossless-claw" ,
2035+ status : "skipped" ,
2036+ message,
2037+ } ,
2038+ ] ) ;
2039+ } ) ;
2040+
19402041 it . each ( [
19412042 {
19422043 source : "npm" ,
@@ -3864,6 +3965,7 @@ describe("updateNpmInstalledPlugins", () => {
38643965 it ( "reuses the recorded managed extensions root when updating external plugins" , async ( ) => {
38653966 const installPath = "/var/openclaw/extensions/demo" ;
38663967 const extensionsDir = "/var/openclaw/extensions" ;
3968+ const expectedExtensionsDir = path . resolve ( extensionsDir ) ;
38673969 installPluginFromNpmSpecMock . mockResolvedValue (
38683970 createSuccessfulNpmUpdateResult ( {
38693971 pluginId : "demo" ,
@@ -3947,7 +4049,6 @@ describe("updateNpmInstalledPlugins", () => {
39474049 pluginIds : [ "demo" ] ,
39484050 } ) ;
39494051
3950- const expectedExtensionsDir = path . resolve ( extensionsDir ) ;
39514052 expect ( npmInstallCall ( ) ?. extensionsDir ) . toBe ( expectedExtensionsDir ) ;
39524053 expect ( clawHubInstallCall ( ) ?. extensionsDir ) . toBe ( expectedExtensionsDir ) ;
39534054 expect ( marketplaceInstallCall ( ) ?. extensionsDir ) . toBe ( expectedExtensionsDir ) ;
0 commit comments