@@ -161,6 +161,7 @@ export class ClineProvider
161161 public static readonly sideBarId = `${ Package . name } .SidebarProvider`
162162 public static readonly tabPanelId = `${ Package . name } .TabPanelProvider`
163163 private static activeInstances : Set < ClineProvider > = new Set ( )
164+ private static nextViewId = 0
164165 private disposables : vscode . Disposable [ ] = [ ]
165166 private webviewDisposables : vscode . Disposable [ ] = [ ]
166167 private view ?: vscode . WebviewView | vscode . WebviewPanel
@@ -238,8 +239,9 @@ export class ClineProvider
238239 mdmService ?: MdmService ,
239240 ) {
240241 super ( )
241- // Initialize viewId based on renderContext and instance counter for uniqueness
242- this . viewId = `${ renderContext } -${ ClineProvider . activeInstances . size } `
242+ // Initialize viewId based on renderContext and monotonically increasing instance identifier for uniqueness.
243+ // activeInstances is used for visibility/iteration checks, so we keep tracking instances separately.
244+ this . viewId = `${ renderContext } -${ ClineProvider . nextViewId ++ } `
243245 ClineProvider . activeInstances . add ( this )
244246 this . currentWorkspacePath = getWorkspacePath ( )
245247 this . pendingEditOperations = new PendingEditOperationStore (
@@ -418,18 +420,33 @@ export class ClineProvider
418420 }
419421 }
420422
423+ /**
424+ * Derive a view-specific ContextProxy key for persisting view-local state.
425+ * Uses the current viewId so each parallel tab restores its own values on recreation.
426+ */
427+ private viewStateKeyFor ( key : "mode" | "currentApiConfigName" | "apiConfiguration" ) : string {
428+ return `__view_state_${ this . viewId } _${ key } `
429+ }
430+
421431 /**
422432 * Loads initial state from global state into the view-local state buffer.
423- * This allows each provider instance to have its own isolated state for fields like mode,
424- * apiConfiguration, etc., while still sharing the same ContextProxy singleton .
433+ * For mode, currentApiConfigName, and apiConfiguration, reads from the view-specific key first;
434+ * falls back to the shared key for backward compatibility with existing persisted state .
425435 */
426436 private async loadViewState ( ) : Promise < void > {
427437 try {
428438 const stateValues = this . contextProxy . getValues ( )
429439 const providerSettings = this . contextProxy . getProviderSettings ( )
440+
441+ // Try view-specific keys first, then fall back to shared keys for backward compatibility.
442+ const getViewSpecificValue = ( sharedKey : "mode" | "currentApiConfigName" ) => {
443+ const viewKey = this . viewStateKeyFor ( sharedKey )
444+ return ( this . contextProxy . getValue ( viewKey as any ) as any ) ?? stateValues [ sharedKey ]
445+ }
446+
430447 this . viewLocalState = {
431- mode : stateValues . mode ,
432- currentApiConfigName : stateValues . currentApiConfigName ,
448+ mode : getViewSpecificValue ( " mode" ) ,
449+ currentApiConfigName : getViewSpecificValue ( " currentApiConfigName" ) ,
433450 apiConfiguration : providerSettings ,
434451 customModePrompts : stateValues . customModePrompts ,
435452 modeApiConfigs : stateValues . modeApiConfigs ,
@@ -443,7 +460,7 @@ export class ClineProvider
443460 }
444461
445462 /**
446- * Save a single view-local state value and sync to global state.
463+ * Save a single view-local state value and sync to global state using a view-specific key .
447464 * This allows each Provider instance to have its own mode/apiConfig for parallel mode support.
448465 */
449466 private async saveViewState ( key : keyof ExtensionState , value : any ) : Promise < void > {
@@ -454,8 +471,12 @@ export class ClineProvider
454471 this . viewLocalState [ key ] = value
455472 }
456473
457- // Also write to ContextProxy so other Provider instances can see it when they sync
458- await this . contextProxy . setValue ( key as any , value )
474+ // Persist to view-specific ContextProxy key for mode/currentApiConfigName/apiConfiguration,
475+ // so recreated views restore their own values instead of the last writer's shared state.
476+ if ( key === "mode" || key === "currentApiConfigName" || key === "apiConfiguration" ) {
477+ const viewKey = this . viewStateKeyFor ( key as "mode" | "currentApiConfigName" | "apiConfiguration" )
478+ await this . contextProxy . setValue ( viewKey as any , value )
479+ }
459480
460481 this . log ( `[saveViewState] Saved ${ String ( key ) } for viewId ${ this . viewId } ` )
461482 }
@@ -1642,7 +1663,9 @@ export class ClineProvider
16421663 }
16431664 } else {
16441665 // If no saved config for this mode, save current config as default.
1645- const currentApiConfigNameAfter = this . getGlobalState ( "currentApiConfigName" )
1666+ // Use view-local state (via getState()) so another tab's global write doesn't supply
1667+ // this view's configuration when running in parallel mode.
1668+ const { currentApiConfigName : currentApiConfigNameAfter } = await this . getState ( )
16461669
16471670 if ( currentApiConfigNameAfter ) {
16481671 const config = listApiConfig . find ( ( c ) => c . name === currentApiConfigNameAfter )
@@ -1741,6 +1764,10 @@ export class ClineProvider
17411764 this . contextProxy . setProviderSettings ( providerSettings ) ,
17421765 ] )
17431766
1767+ // Update view-local state for parallel mode support.
1768+ this . _updateViewLocalStateFromMutation ( { currentApiConfigName : name } as Partial < RooCodeSettings > )
1769+ this . viewLocalState . apiConfiguration = providerSettings
1770+
17441771 // Change the provider for the current task.
17451772 // TODO: We should rename `buildApiHandler` for clarity (e.g. `getProviderClient`).
17461773 this . updateTaskApiHandlerIfNeeded ( providerSettings , { forceRebuild : true } )
@@ -1783,6 +1810,9 @@ export class ClineProvider
17831810 listApiConfigMeta : entries ,
17841811 } )
17851812
1813+ // Update view-local state for parallel mode support.
1814+ this . _updateViewLocalStateFromMutation ( { currentApiConfigName : profileToActivate } )
1815+
17861816 await this . postStateToWebview ( )
17871817 }
17881818
@@ -1830,8 +1860,8 @@ export class ClineProvider
18301860 this . contextProxy . setProviderSettings ( providerSettings ) ,
18311861 ] )
18321862
1833- // Save to view-local state for parallel mode support.
1834- await this . saveViewState ( " currentApiConfigName" , name )
1863+ // Update view-local state for parallel mode support.
1864+ this . _updateViewLocalStateFromMutation ( { currentApiConfigName : name } as Partial < RooCodeSettings > )
18351865 this . viewLocalState . apiConfiguration = providerSettings
18361866
18371867 const { mode } = await this . getState ( )
@@ -2966,6 +2996,7 @@ export class ClineProvider
29662996
29672997 public async setValue < K extends keyof RooCodeSettings > ( key : K , value : RooCodeSettings [ K ] ) {
29682998 await this . contextProxy . setValue ( key , value )
2999+ this . _updateViewLocalStateFromMutation ( { [ key ] : value } )
29693000 }
29703001
29713002 public getValue < K extends keyof RooCodeSettings > ( key : K ) {
@@ -2978,6 +3009,48 @@ export class ClineProvider
29783009
29793010 public async setValues ( values : RooCodeSettings ) {
29803011 await this . contextProxy . setValues ( values )
3012+ this . _updateViewLocalStateFromMutation ( values )
3013+ }
3014+
3015+ /**
3016+ * Update or invalidate viewLocalState when ContextProxy is mutated via setValues, setValue,
3017+ * profile upsert/activation/deletion, or resetState. This ensures the local cache stays in
3018+ * sync with global state changes that would otherwise be invisible behind mergedStateValues.
3019+ */
3020+ private _updateViewLocalStateFromMutation ( values : Partial < RooCodeSettings > ) : void {
3021+ if ( "mode" in values ) {
3022+ const val = values . mode
3023+ if ( val === undefined || val === null ) {
3024+ delete this . viewLocalState . mode
3025+ } else {
3026+ this . viewLocalState . mode = val as any
3027+ }
3028+ }
3029+
3030+ if ( "currentApiConfigName" in values ) {
3031+ const val = values . currentApiConfigName
3032+ if ( val === undefined || val === null ) {
3033+ delete this . viewLocalState . currentApiConfigName
3034+ } else {
3035+ this . viewLocalState . currentApiConfigName = val as any
3036+ }
3037+ }
3038+
3039+ if ( "apiConfiguration" in values ) {
3040+ const val = ( values as any ) . apiConfiguration
3041+ if ( val === undefined || val === null ) {
3042+ delete this . viewLocalState . apiConfiguration
3043+ } else {
3044+ this . viewLocalState . apiConfiguration = val
3045+ }
3046+ }
3047+ }
3048+
3049+ /**
3050+ * Clear view-local state cache so that getState() falls back to ContextProxy defaults.
3051+ */
3052+ private _clearViewLocalState ( ) : void {
3053+ this . viewLocalState = { }
29813054 }
29823055
29833056 // dev
@@ -3006,6 +3079,8 @@ export class ClineProvider
30063079 }
30073080
30083081 await this . contextProxy . resetAllState ( )
3082+ this . _clearViewLocalState ( )
3083+
30093084 await this . providerSettingsManager . resetAllConfigs ( )
30103085 await this . customModesManager . resetCustomModes ( )
30113086 await this . removeClineFromStack ( )
0 commit comments