d81b801fb4
20s withTimeout() on the boot() and soft-switch paths (use-gateway-boot.ts), since these are IPC round-trips into the main process with no timeout of their own — a wedged main-process round-trip hangs the awaiting caller forever instead of surfacing a failure. Every other production call site of the same IPC pair was still unbounded: - store/gateway.ts's openSecondary() and sharedPrimaryRoute() — the actual connection-establishment underneath requestGatewayForProfile/Agent, ensureGatewayForProfile/Agent, and every other exported routing entry point that opens a non-primary profile's socket. - use-gateway-request.ts's on-demand reconnect (the primary gateway's "not connected" retry path hit by every RPC). - voice-playback.ts's resolveSpeakStreamUrl(). - api/plugins.ts's activeConnection() (pluginSocket's connect()). Extracted RECONNECT_ATTEMPT_TIMEOUT_MS into the shared lib/with-timeout.ts (previously local to use-gateway-boot.ts) so every call site uses the same budget instead of duplicating the constant. Regression tests mirror the existing use-gateway-boot.test.tsx hang-repro pattern: wedge getConnection()/getConnectionFor() with a never-resolving promise, advance fake timers past the 20s bound, assert the caller settles instead of hanging. Mutation-verified: reverted the production fix (kept tests) and confirmed the 6 new tests fail — 4 by genuinely timing out at the vitest level, 2 by TypeError on the not-yet-exported activeConnection — restored the fix and confirmed all 70 tests across the gateway/voice/boot/ plugins suites pass, with tsc -p . --noEmit clean throughout.