@@ -196,11 +196,37 @@ export class ClineProvider
196196 private globalStateWriteThroughTimer : ReturnType < typeof setTimeout > | null = null
197197 private static readonly GLOBAL_STATE_WRITE_THROUGH_DEBOUNCE_MS = 5000 // 5 seconds
198198 private static readonly PENDING_OPERATION_TIMEOUT_MS = 30000 // 30 seconds
199+ private providerProfileMutationQueue = Promise . resolve ( )
199200
200201 private runDelegationTransition < T > ( parentTaskId : string , fn : ( ) => Promise < T > ) : Promise < T > {
201202 this . delegationTransitionLocks ??= new Map ( )
202203 return runDelegationTransition ( this . delegationTransitionLocks , parentTaskId , fn )
203204 }
205+
206+ private enqueueProviderProfileMutation < T > ( fn : ( ) => Promise < T > ) : Promise < T > {
207+ const run = this . providerProfileMutationQueue . then ( fn , fn )
208+ const callerResult = this . withProviderProfileMutationTimeout ( run )
209+ this . providerProfileMutationQueue = run . then (
210+ ( ) => undefined ,
211+ ( ) => undefined ,
212+ )
213+ return callerResult
214+ }
215+
216+ private withProviderProfileMutationTimeout < T > ( operation : Promise < T > ) : Promise < T > {
217+ let timeoutId : ReturnType < typeof setTimeout > | undefined
218+ const timeout = new Promise < never > ( ( _ , reject ) => {
219+ timeoutId = setTimeout ( ( ) => {
220+ reject ( new Error ( "Provider profile mutation timed out" ) )
221+ } , ClineProvider . PENDING_OPERATION_TIMEOUT_MS )
222+ } )
223+
224+ return Promise . race ( [ operation , timeout ] ) . finally ( ( ) => {
225+ if ( timeoutId ) {
226+ clearTimeout ( timeoutId )
227+ }
228+ } )
229+ }
204230 private readonly pendingEditOperations : PendingEditOperationStore
205231
206232 private cloudOrganizationsCache : CloudOrganizationMembership [ ] | null = null
@@ -1506,9 +1532,15 @@ export class ClineProvider
15061532 /**
15071533 * Handle switching to a new mode, including updating the associated API configuration
15081534 * @param newMode The mode to switch to
1535+ * @param targetTask The task whose in-memory mode should be updated. Defaults to the
1536+ * current task. Pass null to apply only global mode/profile effects for a pending child.
15091537 */
1510- public async handleModeSwitch ( newMode : Mode ) {
1511- const task = this . getCurrentTask ( )
1538+ public async handleModeSwitch ( newMode : Mode , targetTask : Task | null | undefined = this . getCurrentTask ( ) ) {
1539+ return this . enqueueProviderProfileMutation ( ( ) => this . handleModeSwitchUnlocked ( newMode , targetTask ) )
1540+ }
1541+
1542+ private async handleModeSwitchUnlocked ( newMode : Mode , targetTask : Task | null | undefined ) : Promise < void > {
1543+ const task = targetTask
15121544
15131545 if ( task ) {
15141546 TelemetryService . instance . captureModeSwitch ( task . taskId , newMode )
@@ -1545,7 +1577,9 @@ export class ClineProvider
15451577 // If workspace lock is on, keep the current API config — don't load mode-specific config
15461578 const lockApiConfigAcrossModes = this . context . workspaceState . get ( "lockApiConfigAcrossModes" , false )
15471579 if ( lockApiConfigAcrossModes ) {
1548- await this . postStateToWebview ( )
1580+ if ( targetTask !== null ) {
1581+ await this . postStateToWebview ( )
1582+ }
15491583 return
15501584 }
15511585
@@ -1571,7 +1605,10 @@ export class ClineProvider
15711605 const hasActualSettings = ! ! fullProfile . apiProvider
15721606
15731607 if ( hasActualSettings ) {
1574- await this . activateProviderProfile ( { name : profile . name } )
1608+ await this . activateProviderProfileUnlocked (
1609+ { name : profile . name } ,
1610+ targetTask === null ? { skipCurrentTaskRebuild : true } : undefined ,
1611+ )
15751612 } else {
15761613 // The task will continue with the current/default configuration.
15771614 }
@@ -1591,7 +1628,9 @@ export class ClineProvider
15911628 }
15921629 }
15931630
1594- await this . postStateToWebview ( )
1631+ if ( targetTask !== null ) {
1632+ await this . postStateToWebview ( )
1633+ }
15951634 }
15961635
15971636 // Provider Profile Management
@@ -1607,8 +1646,9 @@ export class ClineProvider
16071646 */
16081647 private updateTaskApiHandlerIfNeeded (
16091648 providerSettings : ProviderSettings ,
1610- options : { forceRebuild ?: boolean } = { } ,
1649+ options : { forceRebuild ?: boolean ; skipCurrentTaskRebuild ?: boolean } = { } ,
16111650 ) : void {
1651+ if ( options . skipCurrentTaskRebuild ) return
16121652 const task = this . getCurrentTask ( )
16131653 if ( ! task ) return
16141654
@@ -1724,7 +1764,11 @@ export class ClineProvider
17241764 await this . postStateToWebview ( )
17251765 }
17261766
1727- private async persistStickyProviderProfileToCurrentTask ( apiConfigName : string ) : Promise < void > {
1767+ private async persistStickyProviderProfileToCurrentTask (
1768+ apiConfigName : string ,
1769+ options : { skipCurrentTaskRebuild ?: boolean } = { } ,
1770+ ) : Promise < void > {
1771+ if ( options . skipCurrentTaskRebuild ) return
17281772 const task = this . getCurrentTask ( )
17291773 if ( ! task ) {
17301774 return
@@ -1754,12 +1798,28 @@ export class ClineProvider
17541798
17551799 async activateProviderProfile (
17561800 args : { name : string } | { id : string } ,
1757- options ?: { persistModeConfig ?: boolean ; persistTaskHistory ?: boolean } ,
1801+ options ?: {
1802+ persistModeConfig ?: boolean
1803+ persistTaskHistory ?: boolean
1804+ skipCurrentTaskRebuild ?: boolean
1805+ } ,
17581806 ) {
1807+ return this . enqueueProviderProfileMutation ( ( ) => this . activateProviderProfileUnlocked ( args , options ) )
1808+ }
1809+
1810+ private async activateProviderProfileUnlocked (
1811+ args : { name : string } | { id : string } ,
1812+ options ?: {
1813+ persistModeConfig ?: boolean
1814+ persistTaskHistory ?: boolean
1815+ skipCurrentTaskRebuild ?: boolean
1816+ } ,
1817+ ) : Promise < void > {
17591818 const { name, id, ...providerSettings } = await this . providerSettingsManager . activateProfile ( args )
17601819
17611820 const persistModeConfig = options ?. persistModeConfig ?? true
17621821 const persistTaskHistory = options ?. persistTaskHistory ?? true
1822+ const skipCurrentTaskRebuild = options ?. skipCurrentTaskRebuild ?? false
17631823
17641824 // See `upsertProviderProfile` for a description of what this is doing.
17651825 await Promise . all ( [
@@ -1775,17 +1835,19 @@ export class ClineProvider
17751835 }
17761836
17771837 // Change the provider for the current task.
1778- this . updateTaskApiHandlerIfNeeded ( providerSettings , { forceRebuild : true } )
1838+ this . updateTaskApiHandlerIfNeeded ( providerSettings , { forceRebuild : true , skipCurrentTaskRebuild } )
17791839
17801840 // Update the current task's sticky provider profile, unless this activation is
17811841 // being used purely as a non-persisting restoration (e.g., reopening a task from history).
17821842 if ( persistTaskHistory ) {
1783- await this . persistStickyProviderProfileToCurrentTask ( name )
1843+ await this . persistStickyProviderProfileToCurrentTask ( name , { skipCurrentTaskRebuild } )
17841844 }
17851845
1786- await this . postStateToWebview ( )
1846+ if ( ! skipCurrentTaskRebuild ) {
1847+ await this . postStateToWebview ( )
1848+ }
17871849
1788- if ( providerSettings . apiProvider ) {
1850+ if ( providerSettings . apiProvider && ! skipCurrentTaskRebuild ) {
17891851 this . emit ( RooCodeEventName . ProviderProfileChanged , { name, provider : providerSettings . apiProvider } )
17901852 }
17911853 }
0 commit comments