From 386e522c643d8b2035940f47f297d1d48013941a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=AD=E5=90=89=E6=B5=A9?= <1625567290@qq.com> Date: Tue, 18 Aug 2026 22:44:06 +0800 Subject: [PATCH 1/5] fix(runtime-host): give owned-candidate connect a CI-safe election window The promptness claim is Host exit after the first connection closes, not the connect itself. A two-second election misses spawn plus handshake under a full suite and only reports `failed`. Widen the window, keep the concrete reason on the assertion, and map a deadline throw to startup_timeout. Fixes #3190 --- .../src/__tests__/owned-candidate.test.ts | 42 ++++++++++++++++++- .../src/client/connect-or-spawn.ts | 10 ++++- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/packages/runtime-host/src/__tests__/owned-candidate.test.ts b/packages/runtime-host/src/__tests__/owned-candidate.test.ts index 94e3a6b41e..688c406eaa 100644 --- a/packages/runtime-host/src/__tests__/owned-candidate.test.ts +++ b/packages/runtime-host/src/__tests__/owned-candidate.test.ts @@ -47,6 +47,32 @@ test('owned connection keeps a fresh Host alive for its full election window', a } }); +test('owned connect names a missed election instead of a bare failed kind', async () => { + const rootPath = await mkdtemp(join(tmpdir(), 'maka-owned-startup-timeout-')); + const result = await connectOwnedRuntimeHostWithDependencies( + { + rootPath, + surface: 'run', + protocol: { + min: RUNTIME_HOST_PROTOCOL_VERSION, + max: RUNTIME_HOST_PROTOCOL_VERSION, + }, + compositionId: INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID, + electionDeadlineMs: 1, + }, + { + launchCandidate: () => ({ + spawned: new Promise((_, reject) => { + setTimeout(() => reject(new Error('Runtime Host election deadline elapsed')), 20); + }), + }), + }, + ); + + assert.equal(result.kind, 'failed'); + assert.equal(ownedConnectFailure(result), 'failed:startup_timeout'); +}); + test('owned Host exits promptly after its first connection closes', async () => { const rootPath = await mkdtemp(join(tmpdir(), 'maka-owned-first-disconnect-')); const result = await connectOwnedRuntimeHostWithDependencies( @@ -58,12 +84,15 @@ test('owned Host exits promptly after its first connection closes', async () => max: RUNTIME_HOST_PROTOCOL_VERSION, }, compositionId: INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID, - electionDeadlineMs: 2_000, + // Connection is not the promptness claim. Two seconds is enough on an + // idle machine and too little under a full CI suite, where spawn plus + // handshake routinely miss the window and surface as `failed`. + electionDeadlineMs: 8_000, }, { launchCandidate: launchOwnedRuntimeHostCandidate }, ); - assert.equal(result.kind, 'connected'); + assert.equal(result.kind, 'connected', ownedConnectFailure(result)); if (result.kind !== 'connected') return; await result.connection.close(); assert.equal(await result.host.settle(500), true); @@ -138,3 +167,12 @@ test('pre-cancelled hosted execution does not start a Runtime Host', async () => assert.equal(result.kind, 'indeterminate'); assert.deepEqual(await readdir(rootPath), []); }); + +function ownedConnectFailure( + result: Awaited>, +): string { + if (result.kind === 'connected') return 'connected'; + if (result.kind === 'failed') return `failed:${result.reason}`; + if (result.kind === 'upgrade_required') return 'upgrade_required'; + return `incompatible:${result.handshake.replacement}`; +} diff --git a/packages/runtime-host/src/client/connect-or-spawn.ts b/packages/runtime-host/src/client/connect-or-spawn.ts index 962292945c..4abf804007 100644 --- a/packages/runtime-host/src/client/connect-or-spawn.ts +++ b/packages/runtime-host/src/client/connect-or-spawn.ts @@ -143,11 +143,17 @@ export async function connectOwnedRuntimeHostWithDependencies( return { kind: 'failed', reason: 'existing_host' }; } return { kind: 'connected', connection: ownedConnection, host }; - } catch { + } catch (error) { await connection?.close().catch(() => undefined); const host = await launch?.spawned.catch(() => undefined); await host?.settle(1_000); - return { kind: 'failed', reason: 'host_unresponsive' }; + const message = error instanceof Error ? error.message : ''; + return { + kind: 'failed', + reason: message.includes('election deadline elapsed') + ? 'startup_timeout' + : 'host_unresponsive', + }; } } From 15e78b689f8f0840ebc01396e8a808fada81e06f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=AD=E5=90=89=E6=B5=A9?= <1625567290@qq.com> Date: Tue, 18 Aug 2026 23:17:55 +0800 Subject: [PATCH 2/5] fix(runtime-host): preserve owned election failures --- .../src/__tests__/owned-candidate.test.ts | 4 ++-- .../src/client/connect-or-spawn.ts | 18 ++++++++---------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/runtime-host/src/__tests__/owned-candidate.test.ts b/packages/runtime-host/src/__tests__/owned-candidate.test.ts index 688c406eaa..bc66b95fe0 100644 --- a/packages/runtime-host/src/__tests__/owned-candidate.test.ts +++ b/packages/runtime-host/src/__tests__/owned-candidate.test.ts @@ -47,7 +47,7 @@ test('owned connection keeps a fresh Host alive for its full election window', a } }); -test('owned connect names a missed election instead of a bare failed kind', async () => { +test('owned connect preserves a missed election when the candidate reports late', async () => { const rootPath = await mkdtemp(join(tmpdir(), 'maka-owned-startup-timeout-')); const result = await connectOwnedRuntimeHostWithDependencies( { @@ -63,7 +63,7 @@ test('owned connect names a missed election instead of a bare failed kind', asyn { launchCandidate: () => ({ spawned: new Promise((_, reject) => { - setTimeout(() => reject(new Error('Runtime Host election deadline elapsed')), 20); + setTimeout(() => reject(new Error('late candidate report')), 20); }), }), }, diff --git a/packages/runtime-host/src/client/connect-or-spawn.ts b/packages/runtime-host/src/client/connect-or-spawn.ts index 4abf804007..87e3e42026 100644 --- a/packages/runtime-host/src/client/connect-or-spawn.ts +++ b/packages/runtime-host/src/client/connect-or-spawn.ts @@ -127,11 +127,15 @@ export async function connectOwnedRuntimeHostWithDependencies( random: Math.random, }, ); - const host = await launch?.spawned; + const host = await launch?.spawned.catch(() => undefined); if (result.kind !== 'connected' || !host) { if (result.kind === 'connected') await result.connection.close(); await host?.settle(1_000); - return result.kind === 'connected' ? { kind: 'failed', reason: 'existing_host' } : result; + if (result.kind !== 'connected') return result; + return { + kind: 'failed', + reason: launch ? 'host_unresponsive' : 'existing_host', + }; } const ownedConnection = result.connection; connection = ownedConnection; @@ -143,17 +147,11 @@ export async function connectOwnedRuntimeHostWithDependencies( return { kind: 'failed', reason: 'existing_host' }; } return { kind: 'connected', connection: ownedConnection, host }; - } catch (error) { + } catch { await connection?.close().catch(() => undefined); const host = await launch?.spawned.catch(() => undefined); await host?.settle(1_000); - const message = error instanceof Error ? error.message : ''; - return { - kind: 'failed', - reason: message.includes('election deadline elapsed') - ? 'startup_timeout' - : 'host_unresponsive', - }; + return { kind: 'failed', reason: 'host_unresponsive' }; } } From 368409ab17ab6c6dd8495cb47890ffaaeadbd299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=AD=E5=90=89=E6=B5=A9?= <1625567290@qq.com> Date: Tue, 18 Aug 2026 23:47:43 +0800 Subject: [PATCH 3/5] fix(runtime-host): return missed owned elections without waiting Election already bounds candidate startup. Awaiting launch.spawned after a non-connected result hangs connectOwned when the candidate never reports. Return that result immediately and settle a late host in the background. Fixes #3190 --- .../src/__tests__/owned-candidate.test.ts | 60 ++++++++++++------- .../src/client/connect-or-spawn.ts | 20 +++++-- 2 files changed, 52 insertions(+), 28 deletions(-) diff --git a/packages/runtime-host/src/__tests__/owned-candidate.test.ts b/packages/runtime-host/src/__tests__/owned-candidate.test.ts index bc66b95fe0..0c9f81dd80 100644 --- a/packages/runtime-host/src/__tests__/owned-candidate.test.ts +++ b/packages/runtime-host/src/__tests__/owned-candidate.test.ts @@ -47,30 +47,46 @@ test('owned connection keeps a fresh Host alive for its full election window', a } }); -test('owned connect preserves a missed election when the candidate reports late', async () => { +test('owned connect returns a missed election without waiting for a late candidate', async () => { const rootPath = await mkdtemp(join(tmpdir(), 'maka-owned-startup-timeout-')); - const result = await connectOwnedRuntimeHostWithDependencies( - { - rootPath, - surface: 'run', - protocol: { - min: RUNTIME_HOST_PROTOCOL_VERSION, - max: RUNTIME_HOST_PROTOCOL_VERSION, + let launched = 0; + let abandon: ((error: Error) => void) | undefined; + const started = performance.now(); + try { + const result = await connectOwnedRuntimeHostWithDependencies( + { + rootPath, + surface: 'run', + protocol: { + min: RUNTIME_HOST_PROTOCOL_VERSION, + max: RUNTIME_HOST_PROTOCOL_VERSION, + }, + compositionId: INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID, + electionDeadlineMs: 80, }, - compositionId: INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID, - electionDeadlineMs: 1, - }, - { - launchCandidate: () => ({ - spawned: new Promise((_, reject) => { - setTimeout(() => reject(new Error('late candidate report')), 20); - }), - }), - }, - ); - - assert.equal(result.kind, 'failed'); - assert.equal(ownedConnectFailure(result), 'failed:startup_timeout'); + { + launchCandidate: () => { + launched += 1; + return { + spawned: new Promise((_, reject) => { + abandon = reject; + }), + }; + }, + }, + ); + const elapsed = performance.now() - started; + + assert.ok(launched >= 1, 'launchCandidate must run before the election ends'); + assert.equal(result.kind, 'failed'); + assert.equal(ownedConnectFailure(result), 'failed:startup_timeout'); + assert.ok( + elapsed < 400, + `missed election waited ${Math.round(elapsed)}ms on a never-settling candidate`, + ); + } finally { + abandon?.(new Error('abandoned after election')); + } }); test('owned Host exits promptly after its first connection closes', async () => { diff --git a/packages/runtime-host/src/client/connect-or-spawn.ts b/packages/runtime-host/src/client/connect-or-spawn.ts index 87e3e42026..c3f3c511b5 100644 --- a/packages/runtime-host/src/client/connect-or-spawn.ts +++ b/packages/runtime-host/src/client/connect-or-spawn.ts @@ -127,11 +127,13 @@ export async function connectOwnedRuntimeHostWithDependencies( random: Math.random, }, ); + if (result.kind !== 'connected') { + settleOwnedLaunch(launch); + return result; + } const host = await launch?.spawned.catch(() => undefined); - if (result.kind !== 'connected' || !host) { - if (result.kind === 'connected') await result.connection.close(); - await host?.settle(1_000); - if (result.kind !== 'connected') return result; + if (!host) { + await result.connection.close(); return { kind: 'failed', reason: launch ? 'host_unresponsive' : 'existing_host', @@ -149,12 +151,18 @@ export async function connectOwnedRuntimeHostWithDependencies( return { kind: 'connected', connection: ownedConnection, host }; } catch { await connection?.close().catch(() => undefined); - const host = await launch?.spawned.catch(() => undefined); - await host?.settle(1_000); + settleOwnedLaunch(launch); return { kind: 'failed', reason: 'host_unresponsive' }; } } +function settleOwnedLaunch( + launch: ReturnType | undefined, +): void { + if (!launch) return; + void launch.spawned.then((host) => host.settle(1_000)).catch(() => undefined); +} + export async function connectOrSpawnRuntimeHostWithDependencies( input: ConnectOrSpawnRuntimeHostInput, dependencies: ConnectOrSpawnRuntimeHostDependencies, From 11169637f1d58e16639c3884dba3298a2a45cd94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=AD=E5=90=89=E6=B5=A9?= <1625567290@qq.com> Date: Wed, 19 Aug 2026 00:20:03 +0800 Subject: [PATCH 4/5] fix(runtime-host): release late owned candidates instead of killing them A timed-out owned connect no longer settles a late candidate. That path sent SIGKILL after 1s and could terminate a host another client had already adopted. Release the process and let idle-grace / initial-connection policy exit it when unused. Fixes #3190 --- .../src/__tests__/owned-candidate.test.ts | 171 ++++++++++++++++-- .../src/client/connect-or-spawn.ts | 12 +- 2 files changed, 163 insertions(+), 20 deletions(-) diff --git a/packages/runtime-host/src/__tests__/owned-candidate.test.ts b/packages/runtime-host/src/__tests__/owned-candidate.test.ts index 0c9f81dd80..32d327a240 100644 --- a/packages/runtime-host/src/__tests__/owned-candidate.test.ts +++ b/packages/runtime-host/src/__tests__/owned-candidate.test.ts @@ -7,9 +7,12 @@ import { INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID, RUNTIME_HOST_PROTOCOL_VERSION, } from '../protocol/index.js'; -import { connectOwnedRuntimeHostWithDependencies } from '../client/connect-or-spawn.js'; +import { + connectOrSpawnRuntimeHostWithDependencies, + connectOwnedRuntimeHostWithDependencies, +} from '../client/connect-or-spawn.js'; import { runHostedExecution } from '../client/hosted-execution.js'; -import { launchOwnedRuntimeHostCandidate } from '../client/launcher.js'; +import { launchOwnedRuntimeHostCandidate, type OwnedCandidateAttempt } from '../client/launcher.js'; test('owned connection keeps a fresh Host alive for its full election window', async () => { const rootPath = await mkdtemp(join(tmpdir(), 'maka-owned-first-connection-')); @@ -47,11 +50,29 @@ test('owned connection keeps a fresh Host alive for its full election window', a } }); -test('owned connect returns a missed election without waiting for a late candidate', async () => { +test('owned connect returns a missed election without waiting for a late candidate', { + timeout: 15_000, +}, async () => { const rootPath = await mkdtemp(join(tmpdir(), 'maka-owned-startup-timeout-')); let launched = 0; - let abandon: ((error: Error) => void) | undefined; - const started = performance.now(); + let released = 0; + let settled = 0; + let resolveSpawned!: (host: OwnedCandidateAttempt) => void; + let rejectSpawned!: (error: Error) => void; + let spawnedSettled = false; + const spawned = new Promise((resolve, reject) => { + resolveSpawned = resolve; + rejectSpawned = reject; + }); + void spawned.then( + () => { + spawnedSettled = true; + }, + () => { + spawnedSettled = true; + }, + ); + try { const result = await connectOwnedRuntimeHostWithDependencies( { @@ -62,30 +83,123 @@ test('owned connect returns a missed election without waiting for a late candida max: RUNTIME_HOST_PROTOCOL_VERSION, }, compositionId: INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID, - electionDeadlineMs: 80, + electionDeadlineMs: 250, }, { launchCandidate: () => { launched += 1; - return { - spawned: new Promise((_, reject) => { - abandon = reject; - }), - }; + return { spawned }; }, }, ); - const elapsed = performance.now() - started; assert.ok(launched >= 1, 'launchCandidate must run before the election ends'); assert.equal(result.kind, 'failed'); assert.equal(ownedConnectFailure(result), 'failed:startup_timeout'); - assert.ok( - elapsed < 400, - `missed election waited ${Math.round(elapsed)}ms on a never-settling candidate`, + assert.equal(spawnedSettled, false); + + resolveSpawned({ + pid: 4_242, + releaseToEnvironment() { + released += 1; + }, + async settle() { + settled += 1; + return true; + }, + }); + await spawned; + assert.equal(released, 1); + assert.equal(settled, 0); + } finally { + if (!spawnedSettled) rejectSpawned(new Error('abandoned after election')); + } +}); + +test('a late owned candidate stays alive after another client adopts it', { + timeout: 25_000, +}, async () => { + const rootPath = await mkdtemp(join(tmpdir(), 'maka-owned-late-adopt-')); + let launchedHost: OwnedCandidateAttempt | undefined; + let resolveLate!: (host: OwnedCandidateAttempt) => void; + let rejectLate!: (error: Error) => void; + let lateDelivered = false; + const lateSpawned = new Promise((resolve, reject) => { + resolveLate = resolve; + rejectLate = reject; + }); + + try { + const missed = await connectOwnedRuntimeHostWithDependencies( + { + rootPath, + surface: 'run', + protocol: { + min: RUNTIME_HOST_PROTOCOL_VERSION, + max: RUNTIME_HOST_PROTOCOL_VERSION, + }, + compositionId: INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID, + electionDeadlineMs: 500, + }, + { + launchCandidate(input) { + const launch = launchOwnedRuntimeHostCandidate({ + ...input, + initialConnectionTimeoutMs: 10_000, + }); + void launch.spawned.then((host) => { + launchedHost = host; + }); + return { spawned: lateSpawned }; + }, + }, + ); + + assert.equal(missed.kind, 'failed'); + assert.equal(ownedConnectFailure(missed), 'failed:startup_timeout'); + + const host = await waitForDefined( + () => launchedHost, + 8_000, + 'late owned candidate did not spawn', + ); + const adopted = await connectOrSpawnRuntimeHostWithDependencies( + { + rootPath, + surface: 'run', + protocol: { + min: RUNTIME_HOST_PROTOCOL_VERSION, + max: RUNTIME_HOST_PROTOCOL_VERSION, + }, + compositionId: INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID, + candidateEntrypoint: new URL('../execution-candidate-main.js', import.meta.url), + electionDeadlineMs: 8_000, + }, + { + random: Math.random, + launchCandidate() { + throw new Error('adopter must use the late owned candidate'); + }, + }, ); + + try { + assert.equal(adopted.kind, 'connected', adoptedConnectFailure(adopted)); + if (adopted.kind !== 'connected') return; + + lateDelivered = true; + resolveLate(host); + await lateSpawned; + + const diagnostics = await adopted.connection.queryHostDiagnostics(); + assert.equal(diagnostics.pid, host.pid); + assert.doesNotThrow(() => process.kill(host.pid, 0)); + } finally { + if (adopted.kind === 'connected') await adopted.connection.close(); + } } finally { - abandon?.(new Error('abandoned after election')); + if (!lateDelivered) rejectLate(new Error('abandoned after election')); + if (launchedHost) await launchedHost.settle(5_000); } }); @@ -192,3 +306,28 @@ function ownedConnectFailure( if (result.kind === 'upgrade_required') return 'upgrade_required'; return `incompatible:${result.handshake.replacement}`; } + +function adoptedConnectFailure( + result: Awaited>, +): string { + if (result.kind === 'connected') return 'connected'; + if (result.kind === 'failed') return `failed:${result.reason}`; + if (result.kind === 'upgrade_required') return 'upgrade_required'; + return `incompatible:${result.handshake.replacement}`; +} + +async function waitForDefined( + read: () => T | undefined, + timeoutMs: number, + message: string, +): Promise { + const deadline = performance.now() + timeoutMs; + for (;;) { + const value = read(); + if (value !== undefined) return value; + if (performance.now() >= deadline) throw new Error(message); + await new Promise((resolve) => { + setTimeout(resolve, 20); + }); + } +} diff --git a/packages/runtime-host/src/client/connect-or-spawn.ts b/packages/runtime-host/src/client/connect-or-spawn.ts index c3f3c511b5..62cbb7b9d2 100644 --- a/packages/runtime-host/src/client/connect-or-spawn.ts +++ b/packages/runtime-host/src/client/connect-or-spawn.ts @@ -128,7 +128,7 @@ export async function connectOwnedRuntimeHostWithDependencies( }, ); if (result.kind !== 'connected') { - settleOwnedLaunch(launch); + releaseOwnedLaunch(launch); return result; } const host = await launch?.spawned.catch(() => undefined); @@ -151,16 +151,20 @@ export async function connectOwnedRuntimeHostWithDependencies( return { kind: 'connected', connection: ownedConnection, host }; } catch { await connection?.close().catch(() => undefined); - settleOwnedLaunch(launch); + releaseOwnedLaunch(launch); return { kind: 'failed', reason: 'host_unresponsive' }; } } -function settleOwnedLaunch( +function releaseOwnedLaunch( launch: ReturnType | undefined, ): void { if (!launch) return; - void launch.spawned.then((host) => host.settle(1_000)).catch(() => undefined); + void launch.spawned + .then((host) => { + host.releaseToEnvironment(); + }) + .catch(() => undefined); } export async function connectOrSpawnRuntimeHostWithDependencies( From 8ca108e3ee54844fced6b91ea84359e991b0705c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=AD=E5=90=89=E6=B5=A9?= <1625567290@qq.com> Date: Wed, 19 Aug 2026 01:05:29 +0800 Subject: [PATCH 5/5] test(runtime-host): assert late owned cleanup releases instead of settling The adopt regression only checked that the late pid stayed alive. Wrap the delivered candidate so a settle() cleanup cannot pass that check and kill the adopted host after the assertion. Fixes #3190 --- .../src/__tests__/owned-candidate.test.ts | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/packages/runtime-host/src/__tests__/owned-candidate.test.ts b/packages/runtime-host/src/__tests__/owned-candidate.test.ts index 32d327a240..7687bbe733 100644 --- a/packages/runtime-host/src/__tests__/owned-candidate.test.ts +++ b/packages/runtime-host/src/__tests__/owned-candidate.test.ts @@ -95,7 +95,7 @@ test('owned connect returns a missed election without waiting for a late candida assert.ok(launched >= 1, 'launchCandidate must run before the election ends'); assert.equal(result.kind, 'failed'); - assert.equal(ownedConnectFailure(result), 'failed:startup_timeout'); + assert.equal(connectFailure(result), 'failed:startup_timeout'); assert.equal(spawnedSettled, false); resolveSpawned({ @@ -156,7 +156,7 @@ test('a late owned candidate stays alive after another client adopts it', { ); assert.equal(missed.kind, 'failed'); - assert.equal(ownedConnectFailure(missed), 'failed:startup_timeout'); + assert.equal(connectFailure(missed), 'failed:startup_timeout'); const host = await waitForDefined( () => launchedHost, @@ -184,12 +184,26 @@ test('a late owned candidate stays alive after another client adopts it', { ); try { - assert.equal(adopted.kind, 'connected', adoptedConnectFailure(adopted)); + assert.equal(adopted.kind, 'connected', connectFailure(adopted)); if (adopted.kind !== 'connected') return; + let released = 0; + let settled = 0; lateDelivered = true; - resolveLate(host); + resolveLate({ + ...host, + releaseToEnvironment() { + released += 1; + host.releaseToEnvironment(); + }, + async settle(timeoutMs) { + settled += 1; + return host.settle(timeoutMs); + }, + }); await lateSpawned; + assert.equal(released, 1); + assert.equal(settled, 0); const diagnostics = await adopted.connection.queryHostDiagnostics(); assert.equal(diagnostics.pid, host.pid); @@ -222,7 +236,7 @@ test('owned Host exits promptly after its first connection closes', async () => { launchCandidate: launchOwnedRuntimeHostCandidate }, ); - assert.equal(result.kind, 'connected', ownedConnectFailure(result)); + assert.equal(result.kind, 'connected', connectFailure(result)); if (result.kind !== 'connected') return; await result.connection.close(); assert.equal(await result.host.settle(500), true); @@ -298,17 +312,10 @@ test('pre-cancelled hosted execution does not start a Runtime Host', async () => assert.deepEqual(await readdir(rootPath), []); }); -function ownedConnectFailure( - result: Awaited>, -): string { - if (result.kind === 'connected') return 'connected'; - if (result.kind === 'failed') return `failed:${result.reason}`; - if (result.kind === 'upgrade_required') return 'upgrade_required'; - return `incompatible:${result.handshake.replacement}`; -} - -function adoptedConnectFailure( - result: Awaited>, +function connectFailure( + result: + | Awaited> + | Awaited>, ): string { if (result.kind === 'connected') return 'connected'; if (result.kind === 'failed') return `failed:${result.reason}`;