fix(desktop): preserve implicit default local route
This commit is contained in:
@@ -745,18 +745,21 @@ test('registry local route: per-profile override wins when global remote is also
|
||||
assert.deepEqual(route, { delegate: true, poolKey: 'research' })
|
||||
})
|
||||
|
||||
test('registry local route: a concrete remote-only profile is refused on the forced-local branch, including default', () => {
|
||||
test('registry local route: a concrete remote-only profile is refused on the forced-local branch', () => {
|
||||
// globalRemote still force-locals a profile that exists on this machine
|
||||
// (This device must not dial the remote). A profile that exists only on the
|
||||
// remote must not spawn a local child — including default, which the
|
||||
// ordinary existence guard exempts and would otherwise start silently.
|
||||
// (This device must not dial the remote). A named profile that exists only
|
||||
// on the remote must not spawn a local child.
|
||||
const named = resolveRegistryLocalRoute('inbox', { globalRemote: true, localProfileExists: false })
|
||||
const fallback = resolveRegistryLocalRoute('default', { globalRemote: true, localProfileExists: false })
|
||||
|
||||
assert.match(String(named.refuse ?? ''), /Profile "inbox" no longer exists/)
|
||||
assert.equal(named.delegate, false)
|
||||
assert.match(String(fallback.refuse ?? ''), /Profile "default" no longer exists/)
|
||||
assert.equal(fallback.delegate, false)
|
||||
|
||||
// default is $HERMES_HOME, not profiles/default: a profiles/default probe
|
||||
// reports absent, but This device -> default must still open locally.
|
||||
assert.deepEqual(resolveRegistryLocalRoute('default', { globalRemote: true, localProfileExists: false }), {
|
||||
delegate: false,
|
||||
poolKey: 'conn:local::default'
|
||||
})
|
||||
|
||||
const present = resolveRegistryLocalRoute('research', { globalRemote: true, localProfileExists: true })
|
||||
|
||||
|
||||
@@ -220,9 +220,7 @@ export interface RegistryLocalRoute {
|
||||
delegate: boolean
|
||||
/** Pool key for the forced-local child when not delegating. */
|
||||
poolKey: string
|
||||
/** Set when a concrete remote-only profile must not spawn a local child.
|
||||
* Includes `default`: the ordinary existence guard exempts it, so a
|
||||
* forced-local default spawn would otherwise succeed with no error. */
|
||||
/** Set when a concrete remote-only profile must not spawn a local child. */
|
||||
refuse?: string
|
||||
}
|
||||
|
||||
@@ -526,8 +524,9 @@ function normalizedSshTarget(route: { host?: unknown; port?: unknown; user?: unk
|
||||
* the BARE profile key by design, and that slot may already hold the v1
|
||||
* route's REMOTE descriptor — so the forced-local child pools under the
|
||||
* `conn:local::<profile>` form instead (colons are invalid in profile names,
|
||||
* so it cannot collide). A concrete profile that does not exist on this
|
||||
* machine, including default, is refused instead of spawned. A per-profile
|
||||
* so it cannot collide). A concrete named profile that does not exist on
|
||||
* this machine is refused instead of spawned. `default` is `$HERMES_HOME`
|
||||
* itself, so it always exists here and is never refused. A per-profile
|
||||
* remote override still delegates to the legacy profile route.
|
||||
*/
|
||||
export function resolveRegistryLocalRoute(
|
||||
@@ -551,12 +550,13 @@ export function resolveRegistryLocalRoute(
|
||||
if (opts.globalRemote) {
|
||||
const poolKey = `${backendScopePrefix(LOCAL_CONNECTION_ID)}${profileKey}`
|
||||
|
||||
// A concrete profile that does not exist on this machine is remote-only.
|
||||
// Spawning it locally is the #90477 loop (and, for default, a silent
|
||||
// success — the ordinary guard exempts default). Refuse instead. An
|
||||
// unprofiled call is enumeration, not a dial, and a profile that exists
|
||||
// locally still force-locals so "This device" does not dial the remote.
|
||||
if (concrete && opts.localProfileExists === false) {
|
||||
// A concrete named profile that does not exist on this machine is
|
||||
// remote-only. Spawning it locally is the #90477 loop, so refuse. An
|
||||
// unprofiled call is enumeration, not a dial. `default` lives at
|
||||
// $HERMES_HOME, not profiles/default, so This device -> default always
|
||||
// force-locals. A profile that exists locally still force-locals so
|
||||
// "This device" does not dial the remote.
|
||||
if (concrete && profileKey !== 'default' && opts.localProfileExists === false) {
|
||||
return { delegate: false, poolKey, refuse: `Profile "${profileKey}" no longer exists.` }
|
||||
}
|
||||
|
||||
|
||||
@@ -11676,11 +11676,8 @@ async function spawnPoolBackend(
|
||||
// here, and logging "Starting" first left an orphaned line with no READY
|
||||
// and no exit — the exact undiagnosable burst signature in remote-gateway
|
||||
// user bundles (Aug 2026, Dash's report).
|
||||
assertLocalProfileCanStart(
|
||||
profile,
|
||||
profileDeletionGate,
|
||||
key => directoryExists(path.join(HERMES_HOME, 'profiles', key)),
|
||||
{ allowImplicitDefault: !opts.forceLocal }
|
||||
assertLocalProfileCanStart(profile, profileDeletionGate, key =>
|
||||
directoryExists(path.join(HERMES_HOME, 'profiles', key))
|
||||
)
|
||||
rememberLog(`Starting Hermes backend for profile "${profile}" via ${backend.label}`)
|
||||
|
||||
|
||||
@@ -140,16 +140,6 @@ test('assertLocalProfileCanStart rejects a delayed retry after the profile direc
|
||||
assert.doesNotThrow(() => assertLocalProfileCanStart('selena', gate, profile => profile === 'selena'))
|
||||
})
|
||||
|
||||
test('assertLocalProfileCanStart does not exempt default when a forced-local spawn asks', () => {
|
||||
const gate = new ProfileDeletionGate()
|
||||
|
||||
assert.throws(
|
||||
() => assertLocalProfileCanStart('default', gate, () => false, { allowImplicitDefault: false }),
|
||||
/Profile "default" no longer exists/
|
||||
)
|
||||
assert.doesNotThrow(() => assertLocalProfileCanStart('default', gate, () => true, { allowImplicitDefault: false }))
|
||||
})
|
||||
|
||||
test('localProfilePoolKeys returns every local process scope for one profile', () => {
|
||||
assert.deepEqual(localProfilePoolKeys('Selena'), ['selena', 'conn:local::selena'])
|
||||
assert.deepEqual(localProfilePoolKeys(''), [])
|
||||
|
||||
@@ -184,8 +184,7 @@ export class ProfileDeletionGate {
|
||||
export function assertLocalProfileCanStart(
|
||||
profile: unknown,
|
||||
gate: ProfileDeletionGate,
|
||||
profileDirectoryExists: (profile: string) => boolean,
|
||||
opts: { allowImplicitDefault?: boolean } = {}
|
||||
profileDirectoryExists: (profile: string) => boolean
|
||||
): void {
|
||||
const key = String(profile ?? '')
|
||||
.trim()
|
||||
@@ -193,12 +192,7 @@ export function assertLocalProfileCanStart(
|
||||
|
||||
gate.assertCanStart(key)
|
||||
|
||||
// `default` is `$HERMES_HOME`, not `profiles/default`, so the ordinary local
|
||||
// start exempts it. A forced-local spawn must not: that exemption is how a
|
||||
// remote-only default starts a local agent with no error (#90477).
|
||||
const implicitDefault = opts.allowImplicitDefault !== false && key === 'default'
|
||||
|
||||
if (key && !implicitDefault && !profileDirectoryExists(key)) {
|
||||
if (key && key !== 'default' && !profileDirectoryExists(key)) {
|
||||
throw new Error(`Profile "${key}" no longer exists.`)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user