@@ -124,6 +124,11 @@ const MIN_TRANSPORT_STALE_PENDING_RECOVERY_MS = 10_000;
124124const RECOVERABLE_DISPATCH_RETRY_BASE_MS = 1_000 ;
125125const RECOVERABLE_DISPATCH_RETRY_MAX_MS = 8_000 ;
126126const MAX_RECOVERABLE_DISPATCH_RETRIES = 15 ;
127+ // "Provider/session is already busy" while this runtime has no active accepted
128+ // provider turn is almost always a stale SDK-side busy marker. Do not burn the
129+ // full generic retry budget (≈2 minutes): preserve the turn and let
130+ // session-manager relaunch the provider after a few confirmations.
131+ const MAX_RECOVERABLE_BUSY_DISPATCH_RETRIES = 3 ;
127132const MAX_TRANSPORT_STALE_PENDING_RECOVERY_MS = 30 * 60_000 ;
128133const MIN_TRANSPORT_STALE_PENDING_CANCEL_FALLBACK_MS = 50 ;
129134const MAX_TRANSPORT_STALE_PENDING_CANCEL_FALLBACK_MS = 60_000 ;
@@ -1526,8 +1531,10 @@ export class TransportSessionRuntime implements SessionRuntime {
15261531 // drop the message. Re-queue and auto-retry with backoff so the work
15271532 // completes when the provider frees up; only give up (error) once the
15281533 // bounded retry budget is exhausted (a genuinely wedged provider).
1529- if ( providerError . code !== PROVIDER_ERROR_CODES . CANCELLED
1530- && this . requeueAndScheduleRecoverableRetry ( providerError ) ) {
1534+ const isRecoverableBusy = isRecoverableProviderBusyError ( providerError ) ;
1535+ const canRetryRecoverable = providerError . code !== PROVIDER_ERROR_CODES . CANCELLED
1536+ && ( ! isRecoverableBusy || this . _recoverableDispatchRetries < MAX_RECOVERABLE_BUSY_DISPATCH_RETRIES ) ;
1537+ if ( canRetryRecoverable && this . requeueAndScheduleRecoverableRetry ( providerError ) ) {
15311538 return ;
15321539 }
15331540 this . _recoverableDispatchRetries = 0 ;
0 commit comments