Here's how to fix that.

1. Create Inbound adapter which extends default inbound adapter and exposes DeleteFromServer setting:

Class Test.InboundAdapter Extends EnsLib.File.InboundAdapter
{
Parameter SETTINGS = "DeleteFromServer:Basic";
}

2. Create Passthrough Service, which uses your custom adapter:

Class Test.PassthroughService Extends EnsLib.File.PassthroughService
{
Parameter ADAPTER = "Test.InboundAdapter";
}

3. Use your class (2) when you create a new BS, it will have DeleteFromServer property:

I filed an enhancement request, please use DP-422980 as an identifier if you would contact WRC on this topic.

Recently I wrote a snippet to determine which Business Host took to long to stop:

Class Test.ProdStop
{

/// do ##class(Test.ProdStop).Try()
ClassMethod Try()
{
	set production = ##class(Ens.Director).GetActiveProductionName()
	set rs = ..EnabledFunc(production)
	if rs.%SQLCODE && (rs.%SQLCODE '= 100) {
		write $$$FormatText("Can't get enabled items in %1, SQLCode: %2, Message: %3", production, rs.%SQLCODE, rs.%Message)
		quit
	} 
	
	while rs.%Next() {
		set bh = rs.Name
		set start = $zh
		set sc = ##class(Ens.Director).EnableConfigItem(bh, $$$NO, $$$YES)
		set end = $zh
		set duration = $fn(end-start,"",1)
		write !, $$$FormatText("BH: %1, Stopped in: %2, sc: %3", bh,  duration, $case($$$ISOK(sc), $$$YES:1, :$system.Status.GetErrorText(sc))), !
		if duration>60 {
			write !, $$$FormatText("!!!!!!! BH: %1 TOOK TOO lONG !!!!!!!", bh),!
		}
	}
}

Query Enabled(production) As %SQLQuery
{
SELECT 
	Name 
	, PoolSize
FROM Ens_Config.Item 
WHERE 1=1
	AND Production = :production
	AND Enabled = 1
}

}

It stops BHs one by one, measuring how long it took to stop each one.

I would recommend you try to determine which items are taking too long to stop.

Export production before running this code to avoid manually reenabling all the hosts.

I think it would be easier to write ndjson->json converter. Something like this:

ClassMethod nd2json(file, dir)
{
    set dir = ##class(%File).NormalizeDirectory(dir)
    quit:'##class(%File).Exists(file) $$$ERROR($$$GeneralError, "File " _ file _ " does not exist")
    if '##class(%File).DirectoryExists(dir) {
        do ##class(%File).CreateDirectoryChain(dir)
        
        quit:'##class(%File).DirectoryExists(dir) $$$ERROR($$$GeneralError, "Directory " _ dir _ " does not exist and creation failed")
    }
    
    set stream = ##class(%Stream.FileCharacter).%New()
    do stream.LinkToFile(file)
    while 'stream.AtEnd {
        set json = stream.ReadLine($$$MaxStringLength)
        //set out = ##class(%File).TempFilename("json", dir) // random order
        set out = dir _ $tr($j($i(count), 4), " ", 0) _ ".json" // if the order is important
        set outStream = ##class(%Stream.FileCharacter).%New()
        do outStream.LinkToFile(out)
        do outStream.Write(json)
        do outStream.%Save()
        kill outStream
    }
}

Now, why isn't $ZOBJREF() in the documentation?

What's the use case for this function?

Here's some (autotranslated) info about thesefunctions.

Also $zobjref accepts only integers, so you can pass just the part before @:

set a={}
set b={}
set obj1=$zobjref(1)
set obj2=$zobjref("1@Sample.Person")
zw

Results in:

a=<OBJECT REFERENCE>[1@%Library.DynamicObject]
b=<OBJECT REFERENCE>[2@%Library.DynamicObject]
obj1=<OBJECT REFERENCE>[1@%Library.DynamicObject]
obj2=<OBJECT REFERENCE>[1@%Library.DynamicObject]

There's also no guarantee that the object would be the same i.e.:

set a={"a":1}
set b={"b":1}
set aoref = ""_ a
kill a
set c={"c":1}
set obja=$zobjref(aoref)
zw obja
> obja={"c":1}  ; <DYNAMIC OBJECT>