You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by wy...@apache.org on 2026/09/02 09:36:17 UTC

(maka) 01/01: fix(desktop): keep shared session join progress continuous

This is an automated email from the ASF dual-hosted git repository.

M4n5ter pushed a commit to branch fix/session-collaboration-join-continuity
in repository https://gitbox.apache.org/repos/asf/maka.git

commit 509453d1cb8e21d39406e79507722ea5e4dd4063
Author: Wang <m4...@gmail.com>
AuthorDate: Wed Sep 2 17:35:45 2026 +0800

    fix(desktop): keep shared session join progress continuous
    
    Generated-by: Codex
---
 .../runtime-host-collaboration-ipc-main.test.ts    | 35 ++++++++++++
 .../__tests__/runtime-host-desktop-manager.test.ts | 65 ++++++++++++++++++++++
 .../runtime-host-guest-session-mounts.test.ts      | 36 ++++++++++++
 apps/desktop/src/main/runtime-host-boot.ts         |  4 +-
 .../main/runtime-host-collaboration-ipc-main.ts    | 25 +++++++--
 .../src/main/runtime-host-desktop-candidate.ts     |  4 ++
 .../src/main/runtime-host-desktop-manager.ts       | 52 +++++++++++++++--
 .../src/main/runtime-host-guest-session-mounts.ts  | 17 ++++--
 .../runtime-host/src/__tests__/peer-native.test.ts | 10 +++-
 packages/runtime-host/src/client/host-profile.ts   |  7 +++
 packages/runtime-host/src/client/peer-client.ts    |  7 ++-
 11 files changed, 244 insertions(+), 18 deletions(-)

diff --git a/apps/desktop/src/main/__tests__/runtime-host-collaboration-ipc-main.test.ts b/apps/desktop/src/main/__tests__/runtime-host-collaboration-ipc-main.test.ts
index ce64f26182..c6c3d190d9 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-collaboration-ipc-main.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-collaboration-ipc-main.test.ts
@@ -19,6 +19,7 @@
 
 import assert from 'node:assert/strict';
 import test from 'node:test';
+import { RuntimeHostOperationError } from '@maka/runtime-host/client';
 import {
   decodeCollaborationInvitationCode,
   encodeCollaborationInvitationCode,
@@ -149,3 +150,37 @@ test('requires plaintext confirmation and reports the issued invitation routes',
     { kind: 'peer', coordinationRelayCount: 1 },
   );
 });
+
+test('treats an unavailable collaboration authority as an empty background inbox', async () => {
+  const handlers = new Map<string, IpcHandler>();
+  registerRuntimeHostCollaborationIpc(
+    {
+      async queryCollaborationTurnRequests() {
+        throw new RuntimeHostOperationError(
+          'collaboration.turn-request.query',
+          'operation_unavailable',
+          'Runtime Host collaboration authority is unavailable',
+        );
+      },
+    } as unknown as Parameters<typeof registerRuntimeHostCollaborationIpc>[0],
+    {
+      handle(channel, listener) {
+        handlers.set(channel, listener);
+      },
+    },
+    async () => {
+      throw new Error('not used');
+    },
+  );
+  const query = handlers.get('session-collaboration:turn-request:query');
+  assert.ok(query);
+
+  assert.deepEqual(await query({} as Parameters<IpcHandler>[0]), {
+    canRequestTurns: false,
+    requests: [],
+  });
+  await assert.rejects(
+    query({} as Parameters<IpcHandler>[0], 'session-1'),
+    RuntimeHostOperationError,
+  );
+});
diff --git a/apps/desktop/src/main/__tests__/runtime-host-desktop-manager.test.ts b/apps/desktop/src/main/__tests__/runtime-host-desktop-manager.test.ts
index 2bbe8a467c..6869b50584 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-desktop-manager.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-desktop-manager.test.ts
@@ -710,6 +710,50 @@ test('reconnects after a pairing candidate becomes bound to this Client', async
   await manager.close();
 });
 
+test('activates Guest access on a fresh stream without replaying route progress', async () => {
+  const local = candidateHarness({ hostId: 'host-a' });
+  const remoteHostId = 'a'.repeat(64);
+  const pending = candidateHarness({
+    hostId: remoteHostId,
+    finalizeReconnectRequired: true,
+  });
+  const active = candidateHarness({ hostId: remoteHostId });
+  const queue = [local.candidate, pending.candidate, active.candidate];
+  const phases: string[] = [];
+  const routeRefreshes: Array<boolean | undefined> = [];
+  const manager = await startRuntimeHostDesktopManager(
+    {} as DesktopRuntimeHostCandidateStartInput,
+    {
+      startCandidate: async (input) => {
+        if (input.profileTarget) {
+          routeRefreshes.push(input.refreshPeerRoutes);
+          input.onConnectionPhase?.('discovering');
+          input.onConnectionPhase?.('connecting');
+        }
+        return ready(queue.shift()!);
+      },
+      reconnectBackoff: { minMs: 0, maxMs: 0 },
+    },
+  );
+  await manager.mountGuest(
+    peerGuestTarget('shared-session'),
+    undefined,
+    (phase) => phases.push(phase),
+  );
+  let activations = 0;
+
+  await manager.finalizeGuestAccess('shared-session', undefined, () => {
+    activations += 1;
+  });
+
+  assert.deepEqual(phases, ['discovering', 'connecting']);
+  assert.deepEqual(routeRefreshes, [undefined, false]);
+  assert.equal(activations, 1);
+  assert.equal(pending.closeCalls, 1);
+  assert.equal(manager.current('shared-session')?.candidate, active.candidate);
+  await manager.close();
+});
+
 for (const dispatch of ['not_dispatched', 'dispatched'] as const) {
   test(`replays ${dispatch} pairing finalization after connection loss`, async () => {
     const local = candidateHarness({ hostId: 'host-a' });
@@ -1635,3 +1679,24 @@ function remoteTarget(
     credential: `credential-${target}`,
   };
 }
+
+function peerGuestTarget(
+  id: string,
+): NonNullable<DesktopRuntimeHostCandidateStartInput['profileTarget']> {
+  return {
+    profile: {
+      id,
+      name: id,
+      kind: 'remote',
+      transport: {
+        kind: 'libp2p-direct',
+        peerId: '12D3KooWpeer',
+        routeHints: ['/ip4/192.0.2.1/udp/41000/quic-v1'],
+        coordinationRelays: [],
+      },
+      rootId: 'a'.repeat(64),
+      access: 'session_guest',
+    },
+    credential: 'credential-peer',
+  };
+}
diff --git a/apps/desktop/src/main/__tests__/runtime-host-guest-session-mounts.test.ts b/apps/desktop/src/main/__tests__/runtime-host-guest-session-mounts.test.ts
index ba5ee594c5..45fe594828 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-guest-session-mounts.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-guest-session-mounts.test.ts
@@ -61,6 +61,42 @@ test('retains a successful Guest mount and rehydrates the same authority after r
   await second!.close();
 });
 
+test('presents Guest import as one connection followed by access activation', async () => {
+  const progress: string[] = [];
+  const mounts = service(memoryStore(), {
+    mount: async (_target, _signal, onConnectionPhase) => {
+      onConnectionPhase?.('discovering');
+      onConnectionPhase?.('connecting');
+      onConnectionPhase?.('authenticating');
+      onConnectionPhase?.('handshaking');
+      onConnectionPhase?.('waiting_for_ready');
+    },
+    finalizeAccess: async (_mountId, _signal, onAccessActivated) => {
+      onAccessActivated?.();
+    },
+  });
+
+  const result = await mounts.importInvitation(
+    invitation('guest-progress'),
+    false,
+    'import-progress',
+    (phase) => progress.push(phase),
+  );
+
+  assert.equal(result.kind, 'connected');
+  const visibleProgress = progress.filter((phase, index) => phase !== progress[index - 1]);
+  assert.deepEqual(visibleProgress, [
+    'validating_invitation',
+    'discovering_host',
+    'preparing_route',
+    'connecting',
+    'authenticating',
+    'finalizing_access',
+    'loading_session',
+  ]);
+  await mounts.close();
+});
+
 test('persists authenticated route rotation for reconnect and restart', async () => {
   const store = memoryStore();
   let observePeerEndpoint!: (endpoint: HostPeerEndpoint) => void;
diff --git a/apps/desktop/src/main/runtime-host-boot.ts b/apps/desktop/src/main/runtime-host-boot.ts
index 62c4d4f90e..cee0633822 100644
--- a/apps/desktop/src/main/runtime-host-boot.ts
+++ b/apps/desktop/src/main/runtime-host-boot.ts
@@ -593,9 +593,9 @@ const guestSessionMountService = createDesktopGuestSessionMountService({
       },
     );
   },
-  finalizeAccess: async (mountId, signal) => {
+  finalizeAccess: async (mountId, signal, onAccessActivated) => {
     if (!runtimeHostManager) throw new Error('Runtime Host manager is unavailable');
-    await runtimeHostManager.finalizeGuestAccess(mountId, signal);
+    await runtimeHostManager.finalizeGuestAccess(mountId, signal, onAccessActivated);
   },
   unmount: async (mountId) => {
     if (!runtimeHostManager) return;
diff --git a/apps/desktop/src/main/runtime-host-collaboration-ipc-main.ts b/apps/desktop/src/main/runtime-host-collaboration-ipc-main.ts
index afe90c6f66..b55fb3a302 100644
--- a/apps/desktop/src/main/runtime-host-collaboration-ipc-main.ts
+++ b/apps/desktop/src/main/runtime-host-collaboration-ipc-main.ts
@@ -17,6 +17,7 @@
  * under the License.
  */
 
+import { RuntimeHostOperationError } from '@maka/runtime-host/client';
 import type { DesktopRuntimeHostClient } from './runtime-host-client.js';
 import {
   encodeDesktopCollaborationInvitation,
@@ -91,10 +92,18 @@ export function registerRuntimeHostCollaborationIpc(
   handleReconnectableRead(
     ipcMain,
     'session-collaboration:turn-request:query',
-    (_event, sessionId: unknown) =>
-      client.queryCollaborationTurnRequests(
-        sessionId === undefined ? undefined : requiredId(sessionId, 'Session'),
-      ),
+    async (_event, sessionId: unknown) => {
+      const requestedSessionId =
+        sessionId === undefined ? undefined : requiredId(sessionId, 'Session');
+      try {
+        return await client.queryCollaborationTurnRequests(requestedSessionId);
+      } catch (error) {
+        if (requestedSessionId === undefined && isCollaborationInboxUnavailable(error)) {
+          return { canRequestTurns: false, requests: [] };
+        }
+        throw error;
+      }
+    },
   );
   ipcMain.handle(
     'session-collaboration:turn-request:acknowledge',
@@ -127,6 +136,14 @@ export function registerRuntimeHostCollaborationIpc(
   );
 }
 
+function isCollaborationInboxUnavailable(error: unknown): boolean {
+  return (
+    error instanceof RuntimeHostOperationError &&
+    error.operation === 'collaboration.turn-request.query' &&
+    error.code === 'operation_unavailable'
+  );
+}
+
 function requiredId(value: unknown, label: string): string {
   if (
     typeof value !== 'string' ||
diff --git a/apps/desktop/src/main/runtime-host-desktop-candidate.ts b/apps/desktop/src/main/runtime-host-desktop-candidate.ts
index 51fd7a84b7..a2f0685983 100644
--- a/apps/desktop/src/main/runtime-host-desktop-candidate.ts
+++ b/apps/desktop/src/main/runtime-host-desktop-candidate.ts
@@ -205,6 +205,7 @@ export interface DesktopRuntimeHostCandidateStartInput
   readonly onExit?: (details: CandidateExitDetails) => void;
   readonly candidateLaunchBarrier?: RuntimeHostCandidateLaunchBarrier;
   readonly peerClient?: RuntimeHostPeerClient;
+  readonly refreshPeerRoutes?: boolean;
   readonly onConnectionPhase?: (phase: RuntimeHostConnectionPhase) => void;
   readonly onHostStatus?: (status: HostStatusResult) => void;
   readonly profileTarget?: {
@@ -440,6 +441,9 @@ async function startProfileDesktopRuntimeHostCandidate(
       : { handshakeTimeoutMs: input.handshakeTimeoutMs }),
     readyTimeoutMs: input.electionDeadlineMs ?? 45_000,
     ...(input.peerClient === undefined ? {} : { peerClient: input.peerClient }),
+    ...(input.refreshPeerRoutes === undefined
+      ? {}
+      : { refreshPeerRoutes: input.refreshPeerRoutes }),
     ...(input.onConnectionPhase === undefined
       ? {}
       : { onConnectionPhase: input.onConnectionPhase }),
diff --git a/apps/desktop/src/main/runtime-host-desktop-manager.ts b/apps/desktop/src/main/runtime-host-desktop-manager.ts
index 914aeb1b5b..4789227f8f 100644
--- a/apps/desktop/src/main/runtime-host-desktop-manager.ts
+++ b/apps/desktop/src/main/runtime-host-desktop-manager.ts
@@ -79,7 +79,11 @@ export interface RuntimeHostDesktopManager {
     onConnectionPhase?: (phase: RuntimeHostConnectionPhase) => void,
     onHostStatus?: (status: HostStatusResult) => void,
   ): Promise<void>;
-  finalizeGuestAccess(mountId: string, signal?: AbortSignal): Promise<void>;
+  finalizeGuestAccess(
+    mountId: string,
+    signal?: AbortSignal,
+    onAccessActivated?: () => void,
+  ): Promise<void>;
   unmountGuest(mountId: string): Promise<void>;
   disable(profileId: string): Promise<void>;
   waitUntilReady(
@@ -208,6 +212,8 @@ interface DesktopRuntimeHostTargetGeneration {
   hostId?: string;
   lifecycle?: RuntimeHostReconnectLifecycle<DesktopRuntimeHostCandidate>;
   unsubscribeLifecycle?: () => void;
+  connectionPhaseObserver?: (phase: RuntimeHostConnectionPhase) => void;
+  skipPeerRouteRefreshOnce?: boolean;
   lastCandidate?: {
     readonly hostId: string;
     readonly hostEpoch: string;
@@ -355,11 +361,21 @@ class RuntimeHostDesktopManagerImpl implements RuntimeHostDesktopManager {
     return this.#mutateTarget(profileId, () => this.#finalizeAccessCredential(profileId));
   }
 
-  finalizeGuestAccess(mountId: string, signal?: AbortSignal): Promise<void> {
-    return this.#mutateTarget(mountId, () => this.#finalizeAccessCredential(mountId, signal));
+  finalizeGuestAccess(
+    mountId: string,
+    signal?: AbortSignal,
+    onAccessActivated?: () => void,
+  ): Promise<void> {
+    return this.#mutateTarget(mountId, () =>
+      this.#finalizeAccessCredential(mountId, signal, onAccessActivated),
+    );
   }
 
-  async #finalizeAccessCredential(profileId: string, externalSignal?: AbortSignal): Promise<void> {
+  async #finalizeAccessCredential(
+    profileId: string,
+    externalSignal?: AbortSignal,
+    onAccessActivated?: () => void,
+  ): Promise<void> {
     const target = this.#requireTarget(profileId);
     if (target.target.profile.kind !== 'remote') {
       throw new Error('Only remote Runtime Host profiles can finalize pairing');
@@ -390,7 +406,14 @@ class RuntimeHostDesktopManagerImpl implements RuntimeHostDesktopManager {
             () => candidate.client.finalizeAccessCredential(remainingMs),
             signal,
           );
+          notifyAccessActivated(onAccessActivated);
           if (finalized.reconnectRequired) {
+            if (
+              target.target.profile.kind === 'remote' &&
+              target.target.profile.transport.kind === 'libp2p-direct'
+            ) {
+              target.skipPeerRouteRefreshOnce = true;
+            }
             await candidate.close();
             await this.#waitForReadyCandidate(lifecycle, candidate, signal);
           }
@@ -565,6 +588,10 @@ class RuntimeHostDesktopManagerImpl implements RuntimeHostDesktopManager {
     });
     try {
       target.lifecycle = await this.#startLifecycle(target, false, signal);
+      // Import progress describes the initial connection only. Later
+      // reconnects have their own durable target state and must not restart the
+      // one-shot join UI from route discovery.
+      target.connectionPhaseObserver = undefined;
       if (this.#closed) {
         await target.lifecycle.close();
         throw new Error('Desktop Runtime Host manager is closed');
@@ -863,6 +890,8 @@ class RuntimeHostDesktopManagerImpl implements RuntimeHostDesktopManager {
     let takeoverHostEpoch: string | undefined;
     let localRecoveryAttempted = false;
     const inheritedExit = target.input.onExit;
+    let refreshPeerRoutes = target.skipPeerRouteRefreshOnce !== true;
+    target.skipPeerRouteRefreshOnce = false;
     const tryRecoverLocalHost = async (): Promise<boolean> => {
       if (target.input.profileTarget || localRecoveryAttempted || !this.recoverLocalHost) {
         return false;
@@ -889,13 +918,15 @@ class RuntimeHostDesktopManagerImpl implements RuntimeHostDesktopManager {
             isTargetActive: () => this.#ipcMain.isActive(target.epoch),
             isTargetValid: () => target.valid,
             onConnectionPhase: (phase) => {
-              target.input.onConnectionPhase?.(phase);
+              target.connectionPhaseObserver?.(phase);
             },
+            ...(refreshPeerRoutes ? {} : { refreshPeerRoutes: false }),
             signal,
             ...(takeoverHostEpoch === undefined ? {} : { takeoverHostEpoch }),
           },
           target.observations,
         );
+        refreshPeerRoutes = true;
       } catch (error) {
         signal.throwIfAborted();
         if (await tryRecoverLocalHost()) continue;
@@ -1066,6 +1097,9 @@ class RuntimeHostDesktopManagerImpl implements RuntimeHostDesktopManager {
       input,
       target,
       observations,
+      ...(input.onConnectionPhase
+        ? { connectionPhaseObserver: input.onConnectionPhase }
+        : {}),
       state: {
         epoch,
         target,
@@ -1232,6 +1266,14 @@ function pairingFinalizeTimedOut(error: unknown): boolean {
   );
 }
 
+function notifyAccessActivated(observer: (() => void) | undefined): void {
+  try {
+    observer?.();
+  } catch {
+    // Presentation progress cannot control credential finalization.
+  }
+}
+
 function withRuntimeHostTarget(
   input: DesktopRuntimeHostCandidateStartInput,
   profileTarget: DesktopRuntimeHostCandidateStartInput['profileTarget'],
diff --git a/apps/desktop/src/main/runtime-host-guest-session-mounts.ts b/apps/desktop/src/main/runtime-host-guest-session-mounts.ts
index 4b9e5a97e6..a5a008175e 100644
--- a/apps/desktop/src/main/runtime-host-guest-session-mounts.ts
+++ b/apps/desktop/src/main/runtime-host-guest-session-mounts.ts
@@ -135,7 +135,11 @@ export function createDesktopGuestSessionMountService(input: {
     onConnectionPhase?: (phase: RuntimeHostConnectionPhase) => void,
     onPeerEndpoint?: (endpoint: HostPeerEndpoint) => void,
   ) => Promise<void>;
-  readonly finalizeAccess: (mountId: string, signal: AbortSignal) => Promise<void>;
+  readonly finalizeAccess: (
+    mountId: string,
+    signal: AbortSignal,
+    onAccessActivated?: () => void,
+  ) => Promise<void>;
   readonly unmount: (mountId: string) => Promise<void>;
   readonly wait?: (delayMs: number, signal: AbortSignal) => Promise<void>;
   readonly onError?: (error: Error, mount: GuestSessionMount) => void;
@@ -225,7 +229,13 @@ export function createDesktopGuestSessionMountService(input: {
     if (activation.kind === 'import') {
       reportImportProgress(activation.onProgress, 'finalizing_access');
     }
-    const finalization = input.finalizeAccess(mount.mountId, activation.controller.signal);
+    const finalization = input.finalizeAccess(
+      mount.mountId,
+      activation.controller.signal,
+      activation.kind === 'import'
+        ? () => reportImportProgress(activation.onProgress, 'loading_session')
+        : undefined,
+    );
     activation.finalization = finalization;
     try {
       await finalization;
@@ -576,10 +586,9 @@ function collaborationProgressForConnectionPhase(
     case 'connecting':
       return 'connecting';
     case 'authenticating':
-      return 'authenticating';
     case 'handshaking':
     case 'waiting_for_ready':
-      return 'loading_session';
+      return 'authenticating';
   }
 }
 
diff --git a/packages/runtime-host/src/__tests__/peer-native.test.ts b/packages/runtime-host/src/__tests__/peer-native.test.ts
index 1e8c2d033b..c1cccb4200 100644
--- a/packages/runtime-host/src/__tests__/peer-native.test.ts
+++ b/packages/runtime-host/src/__tests__/peer-native.test.ts
@@ -156,7 +156,15 @@ module.exports = {
     native.default.resolveConnect(3);
     await control;
 
-    await client.connect(peerConnectInput('ready'));
+    const preparedBeforeReopen = preparedPeerIds.length;
+    const reopenPhases: string[] = [];
+    await client.connect(
+      { ...peerConnectInput('ready'), refreshRoutes: false },
+      undefined,
+      (phase) => reopenPhases.push(phase),
+    );
+    assert.equal(preparedPeerIds.length, preparedBeforeReopen);
+    assert.deepEqual(reopenPhases, ['connecting']);
     await client.connect(peerConnectInput('fallback'));
     await assert.rejects(client.connect(peerConnectInput('unreachable')), (failure: unknown) => {
       return failure instanceof RuntimeHostPeerError && failure.code === 'transit_unavailable';
diff --git a/packages/runtime-host/src/client/host-profile.ts b/packages/runtime-host/src/client/host-profile.ts
index 4ac5566570..c0f681ff8c 100644
--- a/packages/runtime-host/src/client/host-profile.ts
+++ b/packages/runtime-host/src/client/host-profile.ts
@@ -388,6 +388,7 @@ export async function connectRuntimeHostProfile(
     readonly readyTimeoutMs?: number;
     readonly sshInteraction?: RuntimeHostSshInteraction;
     readonly peerClient?: RuntimeHostPeerClient;
+    readonly refreshPeerRoutes?: boolean;
     readonly onConnectionPhase?: (phase: RuntimeHostConnectionPhase) => void;
     readonly onHostStatus?: (status: HostStatusResult) => void;
   },
@@ -445,6 +446,7 @@ export async function connectRemoteRuntimeHostProfile(
     readonly readyTimeoutMs?: number;
     readonly sshInteraction?: RuntimeHostSshInteraction;
     readonly peerClient?: RuntimeHostPeerClient;
+    readonly refreshPeerRoutes?: boolean;
     readonly onConnectionPhase?: (phase: RuntimeHostConnectionPhase) => void;
     readonly onHostStatus?: (status: HostStatusResult) => void;
   },
@@ -467,6 +469,9 @@ export async function connectRemoteRuntimeHostProfile(
       expectedRootId: input.profile.rootId,
       clientInstanceId: input.clientInstanceId,
       peerClient: requireRuntimeHostPeerClient(input.peerClient),
+      ...(input.refreshPeerRoutes === undefined
+        ? {}
+        : { refreshPeerRoutes: input.refreshPeerRoutes }),
       ...(input.signal === undefined ? {} : { signal: input.signal }),
       ...(input.connectTimeoutMs === undefined ? {} : { connectTimeoutMs: input.connectTimeoutMs }),
       ...(input.handshakeTimeoutMs === undefined
@@ -593,6 +598,7 @@ export async function connectPeerRuntimeHost(input: {
   readonly expectedRootId: string;
   readonly clientInstanceId: string;
   readonly peerClient: RuntimeHostPeerClient;
+  readonly refreshPeerRoutes?: boolean;
   readonly signal?: AbortSignal;
   readonly connectTimeoutMs?: number;
   readonly handshakeTimeoutMs?: number;
@@ -607,6 +613,7 @@ export async function connectPeerRuntimeHost(input: {
       routeHints: input.transport.routeHints,
       coordinationRelays: input.transport.coordinationRelays,
       directDeadlineMs: Math.min(input.connectTimeoutMs ?? 40_000, 120_000),
+      ...(input.refreshPeerRoutes === undefined ? {} : { refreshRoutes: input.refreshPeerRoutes }),
     },
     input.signal,
     input.onConnectionPhase,
diff --git a/packages/runtime-host/src/client/peer-client.ts b/packages/runtime-host/src/client/peer-client.ts
index 705ba0f9fb..998b96dcbc 100644
--- a/packages/runtime-host/src/client/peer-client.ts
+++ b/packages/runtime-host/src/client/peer-client.ts
@@ -37,6 +37,7 @@ export interface RuntimeHostPeerConnectInput {
   readonly coordinationRelays?: readonly string[];
   readonly transitRelayPeerIds?: readonly string[];
   readonly directDeadlineMs: number;
+  readonly refreshRoutes?: boolean;
 }
 
 export type RuntimeHostPeerConnectionPhase = 'discovering' | 'connecting';
@@ -242,8 +243,10 @@ class RuntimeHostPeerClientImpl implements RuntimeHostPeerClient {
     signal?: AbortSignal,
     onPhase?: (phase: RuntimeHostPeerConnectionPhase) => void,
   ): Promise<RuntimeHostPeerNativeStream> {
-    notifyPhase(onPhase, 'discovering');
-    await this.#prepareRoutes(input, signal);
+    if (input.refreshRoutes !== false) {
+      notifyPhase(onPhase, 'discovering');
+      await this.#prepareRoutes(input, signal);
+    }
     notifyPhase(onPhase, 'connecting');
     return this.#connect(input, signal, 'application');
   }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org