mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-24 16:54:43 +00:00
fix(desktop): gate HMR session-survival entirely out of prod builds
Guard the globalThis gateway-state container and the survivor park/adopt calls on the import.meta.hot literal (not the runtime hmrActive() helper), so Vite dead-code-eliminates every HMR path in production. Prod now uses a plain module-local singleton — no globalThis, no Symbol.for — and the survivor module drops out of the bundle entirely. Verified: gatewayRegistryState, gatewaySurvivor, and import.meta.hot are all absent from the prod build. Removes the now-unused hmrActive() export.
This commit is contained in:
parent
1943c1b750
commit
cbea2acbb8
3 changed files with 33 additions and 25 deletions
|
|
@ -24,11 +24,6 @@ function slot(): SurvivorGlobal {
|
|||
return globalThis as unknown as SurvivorGlobal
|
||||
}
|
||||
|
||||
/** True only in a dev server with HMR wired up. Production strips this. */
|
||||
export function hmrActive(): boolean {
|
||||
return Boolean(import.meta.hot)
|
||||
}
|
||||
|
||||
/** Park the live gateway so the next module instance can re-adopt it. */
|
||||
export function stashGatewaySurvivor(survivor: GatewaySurvivor): void {
|
||||
slot()[SURVIVOR_KEY] = survivor
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import {
|
|||
import { $attentionSessionIds, $workingSessionIds, resetTileRuntimeBindings } from '@/store/session-states'
|
||||
import type { RpcEvent } from '@/types/hermes'
|
||||
|
||||
import { hmrActive, stashGatewaySurvivor, survivorIsStale, takeGatewaySurvivor } from './gateway-hmr-survivor'
|
||||
import { stashGatewaySurvivor, survivorIsStale, takeGatewaySurvivor } from './gateway-hmr-survivor'
|
||||
|
||||
// After this many consecutive failed reconnects (≈45s with the 1→15s backoff)
|
||||
// raise a recoverable boot error. Otherwise a dropped remote gateway loops the
|
||||
|
|
@ -342,9 +342,10 @@ export function useGatewayBoot({
|
|||
// HMR adoption: in a dev hot update, the previous effect instance parked its
|
||||
// still-open socket instead of closing it (see the cleanup below). Re-adopt
|
||||
// it so an edit doesn't drop the live agent session. A stale (closed) parked
|
||||
// socket is discarded and we boot fresh. No-op in production (survivor is
|
||||
// always null — the stash path is gated on import.meta.hot).
|
||||
const survivor = takeGatewaySurvivor()
|
||||
// socket is discarded and we boot fresh. No-op in production: import.meta.hot
|
||||
// is undefined there, so this folds to `null` and the whole survivor module
|
||||
// dead-code-eliminates out of the bundle.
|
||||
const survivor = import.meta.hot ? takeGatewaySurvivor() : null
|
||||
const adoptedFromHmr = Boolean(survivor && !survivorIsStale(survivor))
|
||||
|
||||
if (survivor && !adoptedFromHmr) {
|
||||
|
|
@ -595,7 +596,7 @@ export function useGatewayBoot({
|
|||
// gateway store (HMR-stable module state), so they survive untouched.
|
||||
// Production: import.meta.hot is undefined, so this branch never runs and
|
||||
// the original destructive teardown below is byte-for-byte preserved.
|
||||
if (hmrActive() && gateway.connectionState === 'open') {
|
||||
if (import.meta.hot && gateway.connectionState === 'open') {
|
||||
stashGatewaySurvivor({
|
||||
gateway,
|
||||
profile: survivor?.profile ?? $activeGatewayProfile.get(),
|
||||
|
|
|
|||
|
|
@ -60,24 +60,36 @@ interface GatewayRegistryState {
|
|||
|
||||
const STATE_KEY = Symbol.for('hermes.desktop.gatewayRegistryState')
|
||||
|
||||
function gatewayState(): GatewayRegistryState {
|
||||
const store = globalThis as unknown as { [STATE_KEY]?: GatewayRegistryState }
|
||||
function createRegistryState(): GatewayRegistryState {
|
||||
return {
|
||||
config: null,
|
||||
primaryGateway: null,
|
||||
primaryProfile: 'default',
|
||||
activeKey: 'default',
|
||||
secondaries: new Map<string, Secondary>(),
|
||||
// The active gateway instance, exposed for inline message-stream
|
||||
// components (inline ClarifyTool, model overlays) that call gateway
|
||||
// methods without the instance threaded down through props.
|
||||
$gateway: atom<HermesGateway | null>(null)
|
||||
}
|
||||
}
|
||||
|
||||
if (!store[STATE_KEY]) {
|
||||
store[STATE_KEY] = {
|
||||
config: null,
|
||||
primaryGateway: null,
|
||||
primaryProfile: 'default',
|
||||
activeKey: 'default',
|
||||
secondaries: new Map<string, Secondary>(),
|
||||
// The active gateway instance, exposed for inline message-stream
|
||||
// components (inline ClarifyTool, model overlays) that call gateway
|
||||
// methods without the instance threaded down through props.
|
||||
$gateway: atom<HermesGateway | null>(null)
|
||||
}
|
||||
// Dev only: park the singletons on globalThis so an HMR re-eval of this module
|
||||
// (self-accepted at the bottom) hands back the SAME live sockets/atoms instead
|
||||
// of resetting them — that's what keeps the agent session alive across UI edits.
|
||||
// `import.meta.hot` is undefined in production, so Vite dead-code-eliminates the
|
||||
// entire globalThis branch and prod uses a plain module-local singleton — no
|
||||
// globalThis, no Symbol.for. Both realms load the module once, so the container's
|
||||
// shape and lifetime are identical either way.
|
||||
function gatewayState(): GatewayRegistryState {
|
||||
if (import.meta.hot) {
|
||||
const store = globalThis as unknown as { [STATE_KEY]?: GatewayRegistryState }
|
||||
store[STATE_KEY] ??= createRegistryState()
|
||||
|
||||
return store[STATE_KEY]
|
||||
}
|
||||
|
||||
return store[STATE_KEY]
|
||||
return createRegistryState()
|
||||
}
|
||||
|
||||
const g = gatewayState()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue