Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license

This commit is contained in:
yilsem committed 2026-09-05 13:26:46 +03:00
commit 03634b1ca3
11340 files changed
+3442369

No files matched your search

@@ -0,0 +1,71 @@
import { afterEach, describe, expect, it } from 'vitest'
import {
type GatewaySurvivor,
stashGatewaySurvivor,
survivorIsStale,
takeGatewaySurvivor
} from './gateway-hmr-survivor'
// A minimal stand-in for HermesGateway: the survivor cache only reads
// `connectionState`. Cast through unknown so we don't drag the whole class in.
function fakeGateway(state: 'idle' | 'connecting' | 'open' | 'closed' | 'error') {
return { connectionState: state } as unknown as GatewaySurvivor['gateway']
}
function makeSurvivor(state: Parameters<typeof fakeGateway>[0]): GatewaySurvivor {
return { gateway: fakeGateway(state), profile: 'default', connection: null }
}
afterEach(() => {
// Drain any survivor a test parked but didn't take, so cases don't leak across
// the shared globalThis slot.
takeGatewaySurvivor()
})
describe('gateway HMR survivor cache', () => {
it('returns null when nothing is parked', () => {
expect(takeGatewaySurvivor()).toBeNull()
})
it('round-trips a parked survivor', () => {
const survivor = makeSurvivor('open')
stashGatewaySurvivor(survivor)
expect(takeGatewaySurvivor()).toBe(survivor)
})
it('is single-shot — a second take returns null', () => {
stashGatewaySurvivor(makeSurvivor('open'))
expect(takeGatewaySurvivor()).not.toBeNull()
expect(takeGatewaySurvivor()).toBeNull()
})
it('persists across re-imports via the globalThis slot', async () => {
const survivor = makeSurvivor('open')
stashGatewaySurvivor(survivor)
// A fresh import of the module (simulating an HMR module swap) must resolve
// the same underlying store, not a reset one.
const reimported = await import('./gateway-hmr-survivor')
expect(reimported.takeGatewaySurvivor()).toBe(survivor)
})
it('treats open / connecting sockets as adoptable', () => {
expect(survivorIsStale(makeSurvivor('open'))).toBe(false)
expect(survivorIsStale(makeSurvivor('connecting'))).toBe(false)
})
it('treats closed / error / idle sockets as stale', () => {
expect(survivorIsStale(makeSurvivor('closed'))).toBe(true)
expect(survivorIsStale(makeSurvivor('error'))).toBe(true)
expect(survivorIsStale(makeSurvivor('idle'))).toBe(true)
})
it('returns a stale survivor from take() so the caller can close it', () => {
const dead = makeSurvivor('closed')
stashGatewaySurvivor(dead)
const taken = takeGatewaySurvivor()
expect(taken).toBe(dead)
expect(taken && survivorIsStale(taken)).toBe(true)
})
})
@@ -0,0 +1,56 @@
// Keep the live primary gateway socket alive across Vite HMR so editing UI never
// drops the agent session. Stash on dispose, re-adopt on remount. globalThis +
// self-accept so this module's own reload doesn't reset the cache. Prod strips
// import.meta.hot → byte-for-byte unchanged live unmount.
import type { HermesConnection } from '@/global'
import type { HermesGateway } from '@/hermes'
export interface GatewaySurvivor {
gateway: HermesGateway
profile: string
connection: HermesConnection | null
}
// One slot on globalThis, keyed by a process-stable Symbol so repeated imports
// (across hot reloads) resolve the exact same store.
const SURVIVOR_KEY = Symbol.for('hermes.desktop.gatewaySurvivor')
interface SurvivorGlobal {
[SURVIVOR_KEY]?: GatewaySurvivor | null
}
function slot(): SurvivorGlobal {
return globalThis as unknown as SurvivorGlobal
}
/** Park the live gateway so the next module instance can re-adopt it. */
export function stashGatewaySurvivor(survivor: GatewaySurvivor): void {
slot()[SURVIVOR_KEY] = survivor
}
/**
* Take the parked gateway, if any. Single-shot: the slot is cleared on read.
* The caller decides whether to adopt or discard it via `survivorIsStale` — a
* socket that died while parked (e.g. backend restart between edits) is still
* returned so the caller can close it and boot fresh.
*/
export function takeGatewaySurvivor(): GatewaySurvivor | null {
const store = slot()
const survivor = store[SURVIVOR_KEY] ?? null
store[SURVIVOR_KEY] = null
return survivor
}
/** A parked survivor whose socket is no longer open — caller should discard it. */
export function survivorIsStale(survivor: GatewaySurvivor): boolean {
const state = survivor.gateway.connectionState
return state !== 'open' && state !== 'connecting'
}
// Self-accept so editing THIS module doesn't blow away the cache it manages.
if (import.meta.hot) {
import.meta.hot.accept()
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,392 @@
import type { GatewayWsUrlResult } from '@hermes/shared'
import { act, renderHook } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const gatewayMocks = vi.hoisted(() => ({
instances: [] as Array<{
connect: ReturnType<typeof vi.fn>
connectionState: string
request: ReturnType<typeof vi.fn>
wsUrl: string
}>
}))
vi.mock('@/hermes', async importOriginal => {
const actual = await importOriginal<typeof HermesModule>()
class FakeHermesGateway {
connectionState = 'closed'
wsUrl = ''
request = vi.fn()
connect = vi.fn(async (wsUrl: string) => {
this.wsUrl = wsUrl
this.connectionState = 'open'
for (const handler of this.stateHandlers) {
handler('open')
}
})
close = vi.fn(() => {
this.connectionState = 'closed'
for (const handler of this.stateHandlers) {
handler('closed')
}
})
onEvent = vi.fn(() => () => undefined)
onState = vi.fn((handler: (state: string) => void) => {
this.stateHandlers.add(handler)
handler(this.connectionState)
return () => this.stateHandlers.delete(handler)
})
private stateHandlers = new Set<(state: string) => void>()
constructor() {
gatewayMocks.instances.push(this)
}
}
return { ...actual, HermesGateway: FakeHermesGateway }
})
import type * as HermesModule from '@/hermes'
import type { HermesGateway } from '@/hermes'
import {
$gateway,
closeSecondaryGateways,
configureGatewayRegistry,
ensureGatewayForAgent,
setPrimaryGateway
} from '@/store/gateway'
import { $activeGatewayProfile } from '@/store/profile'
import { $connection, $gatewayState } from '@/store/session'
import { useGatewayRequest } from './use-gateway-request'
interface TestGateway {
connect: ReturnType<typeof vi.fn>
connectionState: string
request: ReturnType<typeof vi.fn>
wsUrl?: string
}
const fakeGateway = { connectionState: 'open' } as unknown as HermesGateway
const remoteConnection = {
authMode: 'oauth' as const,
baseUrl: 'https://ssh.example.test',
connectionId: 'ssh-source',
mode: 'remote' as const,
profile: 'research',
remoteIdentity: 'ssh.example.test',
remoteKind: 'ssh' as const,
token: 'remote-token',
wsUrl: 'wss://ssh.example.test/api/ws?ticket=stale'
}
function installRemoteDesktop() {
let mintCount = 0
const getConnection = vi.fn(async (profile?: null | string) => ({
authMode: 'token' as const,
baseUrl: 'http://127.0.0.1:5151',
mode: 'local' as const,
profile: profile ?? 'default',
token: 'local-token',
wsUrl: 'ws://127.0.0.1:5151/api/ws?token=local'
}))
const getConnectionFor = vi.fn(async ({ connectionId, profile }: { connectionId: string; profile: string }) => ({
...remoteConnection,
connectionId,
profile
}))
const getGatewayWsUrl = vi.fn(async () => ({
ok: true as const,
wsUrl: 'ws://127.0.0.1:5151/api/ws?token=fresh-local'
}))
const getGatewayWsUrlFor = vi.fn(
async ({ connectionId, profile }: { connectionId: string; profile: string }): Promise<GatewayWsUrlResult> => {
mintCount += 1
return {
ok: true as const,
wsUrl: `wss://${connectionId}.example.test/api/ws?profile=${profile}&ticket=fresh-${mintCount}`
}
}
)
Object.defineProperty(window, 'hermesDesktop', {
configurable: true,
value: { getConnection, getConnectionFor, getGatewayWsUrl, getGatewayWsUrlFor }
})
return { getConnection, getConnectionFor, getGatewayWsUrl, getGatewayWsUrlFor }
}
function installPrimaryDesktop(authMode: 'oauth' | 'token') {
const getConnection = vi.fn(async (profile?: null | string) => ({
authMode,
baseUrl: authMode === 'oauth' ? 'https://gateway.example.test' : 'http://127.0.0.1:5151',
mode: authMode === 'oauth' ? ('remote' as const) : ('local' as const),
profile: profile ?? 'default',
token: 'primary-token',
wsUrl: authMode === 'oauth' ? 'wss://gateway.example.test/api/ws?ticket=stale' : 'ws://127.0.0.1:5151/api/ws'
}))
const getGatewayWsUrl = vi.fn(async (profile?: null | string) => ({
ok: true as const,
wsUrl:
authMode === 'oauth'
? `wss://gateway.example.test/api/ws?profile=${profile ?? 'default'}&ticket=fresh`
: 'ws://127.0.0.1:5151/api/ws?token=fresh'
}))
const getConnectionFor = vi.fn()
const getGatewayWsUrlFor = vi.fn()
Object.defineProperty(window, 'hermesDesktop', {
configurable: true,
value: { getConnection, getConnectionFor, getGatewayWsUrl, getGatewayWsUrlFor }
})
return { getConnection, getConnectionFor, getGatewayWsUrl, getGatewayWsUrlFor }
}
function makePrimaryGateway(): TestGateway {
return {
connect: vi.fn(async () => undefined),
connectionState: 'open',
request: vi.fn()
}
}
async function activateRemoteGateway() {
const desktop = installRemoteDesktop()
const primary = makePrimaryGateway()
setPrimaryGateway(primary as unknown as HermesGateway, 'default')
$gateway.set(primary as unknown as HermesGateway)
await ensureGatewayForAgent('ssh-source', 'research')
const gateway = $gateway.get() as unknown as TestGateway
expect(gateway).not.toBe(primary)
expect($activeGatewayProfile.get()).toBe('research')
return { desktop, gateway }
}
async function expectSecondaryRecoveryFailure(
gateway: TestGateway,
request: ReturnType<typeof useGatewayRequest>['requestGateway']
) {
const transportError = new Error('connection closed')
gateway.request.mockRejectedValueOnce(transportError)
gateway.connectionState = 'closed'
vi.useFakeTimers()
const retry = request('session.resume').then(
() => undefined,
error => error
)
await act(async () => {
await Promise.resolve()
await Promise.resolve()
await vi.advanceTimersByTimeAsync(8_000)
})
await expect(retry).resolves.toBe(transportError)
expect(gateway.request).toHaveBeenCalledTimes(1)
expect(gateway.connect).toHaveBeenCalledTimes(1)
}
beforeEach(() => {
gatewayMocks.instances.length = 0
closeSecondaryGateways()
setPrimaryGateway(null)
$gateway.set(null)
$connection.set(null)
$gatewayState.set('idle')
$activeGatewayProfile.set('default')
configureGatewayRegistry({
onActiveRouteChanged: profile => $activeGatewayProfile.set(profile),
onEvent: vi.fn()
})
})
afterEach(() => {
vi.useRealTimers()
closeSecondaryGateways()
setPrimaryGateway(null)
$gateway.set(null)
$connection.set(null)
$gatewayState.set('idle')
$activeGatewayProfile.set('default')
Reflect.deleteProperty(window, 'hermesDesktop')
})
describe('useGatewayRequest', () => {
it('exposes the live gateway on the first render, before effects run', () => {
$gateway.set(fakeGateway)
const { result } = renderHook(() => useGatewayRequest())
expect(result.current.gateway).toBe(fakeGateway)
})
it('tracks the gateway when the active socket changes', () => {
const { result } = renderHook(() => useGatewayRequest())
expect(result.current.gateway).toBeNull()
act(() => $gateway.set(fakeGateway))
expect(result.current.gateway).toBe(fakeGateway)
})
it.each([
{ error: new Error('connection closed'), label: 'closed message' },
{ error: new Error('ECONNRESET'), label: 'reset message' },
{ error: Object.assign(new Error('socket failed'), { code: 'ECONNRESET' }), label: 'error code' },
{ error: Object.assign(new Error('socket failed'), { cause: { code: 'ECONNRESET' } }), label: 'cause code' }
])('recovers the registered remote source after a $label failure', async ({ error }) => {
const { desktop, gateway } = await activateRemoteGateway()
gateway.request.mockResolvedValueOnce({ turn: 1 }).mockRejectedValueOnce(error).mockResolvedValueOnce({ turn: 2 })
const { result } = renderHook(() => useGatewayRequest())
await act(async () => {
await expect(result.current.requestGateway('prompt.submit', { text: 'first' })).resolves.toEqual({ turn: 1 })
})
gateway.connectionState = 'closed'
await act(async () => {
await expect(result.current.requestGateway('prompt.submit', { text: 'second' })).resolves.toEqual({ turn: 2 })
})
expect(desktop.getConnectionFor).toHaveBeenCalledTimes(2)
expect(desktop.getConnectionFor).toHaveBeenCalledWith({ connectionId: 'ssh-source', profile: 'research' })
expect(desktop.getGatewayWsUrlFor).toHaveBeenCalledTimes(2)
expect(desktop.getGatewayWsUrlFor).toHaveBeenCalledWith({ connectionId: 'ssh-source', profile: 'research' })
expect(desktop.getConnection).not.toHaveBeenCalled()
expect(desktop.getGatewayWsUrl).not.toHaveBeenCalled()
expect(gateway.connect).toHaveBeenLastCalledWith(expect.stringContaining('ticket=fresh-2'))
})
it('does not reconnect for a non-transport request failure', async () => {
const { desktop, gateway } = await activateRemoteGateway()
const failure = Object.assign(new Error('request rejected'), { code: 'EVALIDATION' })
gateway.request.mockRejectedValueOnce(failure)
const { result } = renderHook(() => useGatewayRequest())
await expect(result.current.requestGateway('session.resume')).rejects.toBe(failure)
expect(desktop.getConnectionFor).toHaveBeenCalledTimes(1)
expect(desktop.getGatewayWsUrlFor).toHaveBeenCalledTimes(1)
expect(gateway.connect).toHaveBeenCalledTimes(1)
})
it('surfaces a real secondary OAuth reauth rejection as the original transport failure', async () => {
const { desktop, gateway } = await activateRemoteGateway()
desktop.getGatewayWsUrlFor.mockImplementation(async () => ({
error: '401 cookie expired',
needsOauthLogin: true,
ok: false as const
}))
const { result } = renderHook(() => useGatewayRequest())
await expectSecondaryRecoveryFailure(gateway, result.current.requestGateway)
expect(desktop.getConnectionFor).toHaveBeenCalledWith({ connectionId: 'ssh-source', profile: 'research' })
expect(desktop.getGatewayWsUrlFor).toHaveBeenCalled()
expect(desktop.getConnection).not.toHaveBeenCalled()
expect(desktop.getGatewayWsUrl).not.toHaveBeenCalled()
})
it('surfaces a failed secondary OAuth ticket mint without using the stale ticket or local bridges', async () => {
const { desktop, gateway } = await activateRemoteGateway()
desktop.getGatewayWsUrlFor.mockRejectedValue(new Error('ticket mint failed'))
const { result } = renderHook(() => useGatewayRequest())
await expectSecondaryRecoveryFailure(gateway, result.current.requestGateway)
expect(desktop.getConnectionFor).toHaveBeenCalledWith({ connectionId: 'ssh-source', profile: 'research' })
expect(desktop.getConnection).not.toHaveBeenCalled()
expect(desktop.getGatewayWsUrl).not.toHaveBeenCalled()
})
it('surfaces a missing optional scoped mint bridge without falling back to a stale ticket or local lookup', async () => {
const { desktop, gateway } = await activateRemoteGateway()
Reflect.deleteProperty(window.hermesDesktop, 'getGatewayWsUrlFor')
const { result } = renderHook(() => useGatewayRequest())
await expectSecondaryRecoveryFailure(gateway, result.current.requestGateway)
expect(desktop.getConnectionFor).toHaveBeenCalledWith({ connectionId: 'ssh-source', profile: 'research' })
expect(desktop.getConnection).not.toHaveBeenCalled()
expect(desktop.getGatewayWsUrl).not.toHaveBeenCalled()
})
it.each([
{ authMode: 'oauth' as const, label: 'primary OAuth' },
{ authMode: 'token' as const, label: 'local primary' }
])('preserves $label recovery', async ({ authMode }) => {
const desktop = installPrimaryDesktop(authMode)
const primary = makePrimaryGateway()
primary.request.mockRejectedValueOnce(new Error('connection closed')).mockResolvedValueOnce({ recovered: true })
setPrimaryGateway(primary as unknown as HermesGateway, 'default')
$gateway.set(primary as unknown as HermesGateway)
$gatewayState.set('closed')
const { result } = renderHook(() => useGatewayRequest())
await act(async () => {
await expect(result.current.requestGateway('session.resume')).resolves.toEqual({ recovered: true })
})
expect(desktop.getConnection).toHaveBeenCalledWith('default')
expect(desktop.getGatewayWsUrl).toHaveBeenCalledWith('default')
expect(desktop.getConnectionFor).not.toHaveBeenCalled()
expect(desktop.getGatewayWsUrlFor).not.toHaveBeenCalled()
})
it('rejects instead of hanging forever when the reconnect getConnection() wedges (#93454)', async () => {
// Repro: a request lands on a dropped socket, the "not connected" catch
// kicks off a reconnect, and the IPC round-trip into main
// (desktop.getConnection) never settles — e.g. a wedged revalidation after
// a liveness-probe trip. Without an internal timeout on that await,
// reconnectingRef never clears and requestGateway hangs forever instead of
// surfacing the original transport error.
vi.useFakeTimers()
const dropped = {
connectionState: 'closed',
request: vi.fn().mockRejectedValue(new Error('connection closed'))
} as unknown as HermesGateway
const getConnection = vi.fn(() => new Promise(() => undefined))
;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = { getConnection }
$gateway.set(dropped)
const { result } = renderHook(() => useGatewayRequest())
const pending = expect(result.current.requestGateway('some.method')).rejects.toThrow('connection closed')
// Advance past the internal reconnect-attempt timeout (20s) — the stalled
// getConnection() await must reject so the reconnect gives up and the
// original transport error surfaces, instead of requestGateway() never
// settling.
await vi.advanceTimersByTimeAsync(20_000)
await pending
})
})
@@ -0,0 +1,203 @@
import { isGatewayReauthRequired, resolveGatewayWsUrl } from '@hermes/shared'
import { useStore } from '@nanostores/react'
import { useCallback, useEffect, useRef } from 'react'
import type { HermesGateway } from '@/hermes'
import { RECONNECT_ATTEMPT_TIMEOUT_MS, withTimeout } from '@/lib/with-timeout'
import { $gateway, ensureActiveGatewayOpen, isActivePrimary } from '@/store/gateway'
import { $activeGatewayProfile } from '@/store/profile'
import { $gatewayState, setConnection } from '@/store/session'
export function useGatewayRequest() {
const gatewayState = useStore($gatewayState)
// Reactive companion to `gatewayRef`. The ref exists so `requestGateway`
// keeps a stable identity and always reaches the live socket, but it is only
// populated by the subscription effect below — i.e. AFTER the first render.
// A component that reads `gatewayRef.current` while rendering therefore sees
// null on mount, and if the connection state doesn't happen to flip
// afterwards it never re-renders to pick the instance up. Anything that needs
// the gateway as a render-time VALUE (props, memo deps) must use this.
const gateway = useStore($gateway) as HermesGateway | null
const gatewayRef = useRef<HermesGateway | null>(null)
const connectionRef = useRef<Awaited<ReturnType<NonNullable<typeof window.hermesDesktop>['getConnection']>> | null>(
null
)
const gatewayStateRef = useRef(gatewayState)
const reconnectingRef = useRef<Promise<HermesGateway | null> | null>(null)
// Holds the reauth error from the most recent failed reconnect so
// requestGateway can surface the gateway's "session expired, sign in again"
// message instead of the opaque "connection closed" that triggered the retry.
const reauthErrorRef = useRef<unknown>(null)
// eslint-disable-next-line no-restricted-syntax -- legitimate non-atom ref write (see eslint rule comment)
useEffect(() => {
gatewayStateRef.current = gatewayState
}, [gatewayState])
// Track the active gateway (primary or a background profile's socket) so
// outbound requests and overlay props always target the focused profile.
useEffect(
() =>
$gateway.subscribe(gateway => {
gatewayRef.current = gateway as HermesGateway | null
}),
[]
)
const ensureGatewayOpen = useCallback(async () => {
const existing = gatewayRef.current
if (!existing) {
return null
}
if (gatewayStateRef.current === 'open') {
return existing
}
if (reconnectingRef.current) {
return reconnectingRef.current
}
reconnectingRef.current = (async () => {
const desktop = window.hermesDesktop
if (!desktop) {
return null
}
reauthErrorRef.current = null
try {
// Reconnect to whichever profile the gateway is currently routed to (not
// always the primary), so a sleep/wake reconnect keeps the user on the
// profile they were chatting in. Both awaits below are IPC round-trips
// into the main process with no timeout of their own (#93454) — a
// wedged main-process round-trip otherwise hangs this await forever,
// latching reconnectingRef.current so every later requestGateway() call
// returns the same never-settling promise. Bound the same way
// use-gateway-boot.ts bounds the primary boot/soft-switch equivalents.
const conn = await withTimeout(
desktop.getConnection($activeGatewayProfile.get()),
RECONNECT_ATTEMPT_TIMEOUT_MS,
'Timed out reconnecting to Hermes backend'
)
connectionRef.current = conn
setConnection(conn)
// Re-mint the WS URL before reconnecting. OAuth tickets are single-use
// and short-lived, so the cached conn.wsUrl ticket is dead here;
// resolveGatewayWsUrl() never connects with a stale ticket. An explicit
// auth rejection becomes a reauth error; transport failures remain
// retryable. Stash only the former so requestGateway can show the
// actionable "sign in again" message.
const wsUrl = await withTimeout(
resolveGatewayWsUrl(desktop, conn),
RECONNECT_ATTEMPT_TIMEOUT_MS,
'Timed out re-minting the gateway WebSocket URL'
)
await existing.connect(wsUrl)
return existing
} catch (error) {
if (isGatewayReauthRequired(error)) {
reauthErrorRef.current = error
}
connectionRef.current = null
setConnection(null)
return null
} finally {
reconnectingRef.current = null
}
})()
return reconnectingRef.current
}, [])
const requestGateway = useCallback(
async <T>(method: string, params: Record<string, unknown> = {}, timeoutMs?: number, signal?: AbortSignal) => {
const gateway = gatewayRef.current
if (!gateway) {
throw new Error('Hermes gateway unavailable')
}
try {
return await gateway.request<T>(method, params, timeoutMs, signal)
} catch (error) {
if (!isGatewayTransportError(error)) {
throw error
}
// Primary keeps the OAuth-aware reconnect (remote gateways re-mint a
// single-use ticket). Background profiles stay on the registry's
// connection-owned reconnect path, including composite remote/SSH
// sources.
const recovered = isActivePrimary() ? await ensureGatewayOpen() : await ensureActiveGatewayOpen()
if (!recovered) {
// Prefer the reauth error from the failed reconnect (OAuth session
// expired) over the generic transport error that triggered the retry.
const reauthError = reauthErrorRef.current
reauthErrorRef.current = null
if (reauthError) {
throw reauthError
}
throw error
}
return recovered.request<T>(method, params, timeoutMs, signal)
}
},
[ensureGatewayOpen]
)
return { connectionRef, gateway, gatewayRef, requestGateway }
}
const GATEWAY_TRANSPORT_ERROR_CODES = new Set([
'ECONNABORTED',
'ECONNREFUSED',
'ECONNRESET',
'EHOSTUNREACH',
'ENETUNREACH',
'ENOTFOUND',
'EPIPE',
'ETIMEDOUT',
'ERR_NETWORK',
'ERR_SOCKET_CLOSED'
])
function errorCode(value: unknown): string | null {
if (typeof value !== 'object' || value === null) {
return null
}
const code = (value as { code?: unknown }).code
return typeof code === 'string' ? code.toUpperCase() : null
}
function isGatewayTransportError(error: unknown): boolean {
const message = error instanceof Error ? error.message : String(error)
if (/not connected|connection closed|connection reset|ECONNRESET/i.test(message)) {
return true
}
const cause = typeof error === 'object' && error !== null ? (error as { cause?: unknown }).cause : undefined
return [error, cause].some(value => {
const code = errorCode(value)
return code !== null && GATEWAY_TRANSPORT_ERROR_CODES.has(code)
})
}