@@ -2254,9 +2254,8 @@ export class ClineProvider
22542254 removedModeConfigs ,
22552255 )
22562256
2257- // Fenced behind the abort, and read from the manager rather than the
2258- // `listApiConfigMeta` cache, which never observed a profile created while the
2259- // purge held the lock.
2257+ // Fenced behind the abort, and read from the manager rather than the cache,
2258+ // which never observed a profile created while the purge held the lock.
22602259 await this . contextProxy . setValues ( {
22612260 listApiConfigMeta : await this . providerSettingsManager . listConfig ( ) ,
22622261 modeApiConfigs : await this . providerSettingsManager . getModeApiConfigs ( ) ,
@@ -2266,18 +2265,15 @@ export class ClineProvider
22662265
22672266 // The delete is committed, so the profile's credentials go with it. This branch
22682267 // activates no replacement, so no `setProviderSettings` write runs the ownership
2269- // clear-out: without an explicit purge the deleted profile's secrets stay in the
2270- // cache and in SecretStorage until some unrelated future write happens to name a
2271- // provider that does not own them, which may never happen.
2268+ // clear-out: without an explicit purge the secrets stay in the cache and in
2269+ // SecretStorage until some unrelated future write names a provider that does not
2270+ // own them, which may never happen.
22722271 //
22732272 // The keep-set comes from the providers of the profiles that remain, not from the
2274- // active profile's blob. A blob-derived set is empty exactly when the live
2275- // credential needs protecting: the `api.getConfiguration()` round-trip rewrites a
2276- // profile without its secret fields while keeping `apiProvider`, so a surviving
2277- // OpenRouter profile's blob declares no `openRouterApiKey` even though that
2278- // SecretStorage slot holds its live value. When the deleted profile and a
2279- // remaining profile share a provider and a key name, the slot they would purge
2280- // *is* that profile's credential.
2273+ // active blob: that round-trip strips secret fields while keeping `apiProvider`, so
2274+ // a surviving OpenRouter blob declares no `openRouterApiKey` even though the slot
2275+ // holds its live value. A shared provider+key means the slot they would purge *is*
2276+ // that profile's credential.
22812277 await this . purgeDeletedProfileSecrets (
22822278 undoConfig ,
22832279 await this . getSurvivingProviderSecretKeys ( profileToDelete . name , undoConfig ) ,
@@ -2289,8 +2285,8 @@ export class ClineProvider
22892285 return
22902286 }
22912287
2292- // Read the list from the manager rather than the `listApiConfigMeta` cache: the
2293- // cache only observes writes this provider made .
2288+ // Read the list from the manager, not the cache: the cache only observes this
2289+ // provider's own writes.
22942290 await this . contextProxy . setValues ( {
22952291 listApiConfigMeta : ( await this . providerSettingsManager . listConfig ( ) ) . filter (
22962292 ( { name } ) => name !== profileToDelete . name ,
@@ -2308,8 +2304,8 @@ export class ClineProvider
23082304 ( { name } ) => name !== profileToDelete . name ,
23092305 )
23102306
2311- // Prefer a configured profile, mirroring the mode-switch path which skips entries
2312- // with no `apiProvider`. Fall back to any remaining entry so deletion still works.
2307+ // Prefer a configured profile, mirroring the mode-switch path which skips entries with
2308+ // no `apiProvider`; fall back to any remaining entry so deletion still works.
23132309 const profileToActivate = (
23142310 remainingProfiles . find ( ( { apiProvider } ) => ! ! apiProvider ) ?? remainingProfiles [ 0 ]
23152311 ) ?. name
@@ -2332,31 +2328,29 @@ export class ClineProvider
23322328 removedModeConfigs = ( await this . providerSettingsManager . deleteConfig ( profileToDelete . name ) )
23332329 . removedModeConfigs
23342330
2335- // Route through the standard activation path so the effective provider
2336- // settings, mode config, and current-task wiring all end up identical to
2337- // a user activating the replacement profile from the webview. The unlocked
2338- // variant is used because this already runs inside the mutation queue.
2331+ // Route through the standard activation path so the effective provider settings, mode
2332+ // config, and current-task wiring all end up identical to a user activating the
2333+ // replacement profile from the webview. The unlocked variant is used because this
2334+ // already runs inside the mutation queue.
23392335 await this . activateProviderProfileUnlocked ( { name : profileToActivate } , undefined , signal )
23402336
2341- // A timeout may have fired while the activation was in flight. The queued
2342- // rollback is restoring the pre-delete state, so these tail writes must not
2343- // land after it.
2337+ // A timeout may have fired while the activation was in flight. The queued rollback is
2338+ // restoring the pre-delete state, so these tail writes must not land after it.
23442339 if ( signal . aborted ) {
23452340 return
23462341 }
23472342
23482343 // The activation's clear-out is ownership-based, which deliberately preserves a
23492344 // secret the replacement provider owns. When the replacement is the same provider as
2350- // the deleted profile, that rule keeps the deleted profile's credential alive under
2351- // the replacement's name. The delete is explicit intent, so the deleted profile's own
2352- // keys are removed here, after the activation has written the replacement's slice.
2345+ // the deleted profile, that rule keeps the deleted profile's credential alive under the
2346+ // replacement's name. The delete is explicit intent, so the deleted profile's own keys
2347+ // are removed here, after the activation has written the replacement's slice.
23532348 //
23542349 // The keep-set is ownership-derived for the same reason as the inactive branch: a
2355- // blob-derived set is empty when the replacement's blob was written from an
2356- // `api.getConfiguration()` snapshot (secrets stripped), and purging then deletes the
2357- // replacement's live credential from the shared SecretStorage slot. It spans every
2358- // remaining profile, so a same-provider profile that is not the replacement keeps its
2359- // credential too.
2350+ // blob-derived set is empty when the replacement's blob came from a secrets-stripped
2351+ // snapshot, and purging then deletes its live credential from the shared slot. It spans
2352+ // every remaining profile, so a same-provider profile that is not the replacement keeps
2353+ // its credential too.
23602354 await this . purgeDeletedProfileSecrets (
23612355 deletedProfileConfig ,
23622356 await this . getSurvivingProviderSecretKeys ( profileToDelete . name , deletedProfileConfig ) ,
@@ -2425,9 +2419,7 @@ export class ClineProvider
24252419
24262420 try {
24272421 for ( const { name } of await this . providerSettingsManager . listConfig ( ) ) {
2428- if ( name === deletedName || keep . size === deletedKeys . length ) {
2429- continue
2430- }
2422+ if ( name === deletedName ) continue
24312423
24322424 try {
24332425 const { apiProvider } = await this . providerSettingsManager . getProfile ( { name } )
@@ -2539,17 +2531,15 @@ export class ClineProvider
25392531 if ( restored ) {
25402532 this . log ( `Restored provider profile '${ name } ' after a failed replacement activation` )
25412533 } else {
2542- // The restore did not finish, so both stores may still name a profile the
2543- // manager no longer has. The reconcile also re-applies the purge's dropped mode
2544- // bindings and mirrors the mode map, so an aborted restore cannot silently lose
2545- // them.
2534+ // The restore did not finish, so both stores may still name a profile the manager
2535+ // no longer has. The reconcile also re-applies the purge's dropped mode bindings
2536+ // and mirrors the mode map, so an aborted restore cannot silently lose them.
25462537 await this . reconcileActiveProfileAfterFailedRestore ( removedModeConfigs , previousContextValues )
25472538 }
25482539
25492540 // The replacement activation posted state before this rollback landed, and the
2550- // rollback's own writes (the restore slice or the reconcile's fallback) change what
2551- // the webview must show. Without a post here the webview keeps naming the
2552- // replacement while both stores name the restored or reconciled profile.
2541+ // rollback's writes change what the webview must show: without a post it keeps
2542+ // naming the replacement while both stores name the restored profile.
25532543 await this . postStateToWebview ( )
25542544 } ,
25552545 { runToCompletion : true , abortOnTimeout : true } ,
@@ -2627,48 +2617,48 @@ export class ClineProvider
26272617 removedModeConfigs : Record < string , string > ,
26282618 signal : AbortSignal ,
26292619 ) : Promise < boolean > {
2630- if ( config ) {
2631- // Keep `id`: the manager reuses it for the mode associations that point at this profile.
2632- const { name : _name , ...providerSettings } = config
2633- await this . providerSettingsManager . saveConfig ( name , providerSettings )
2620+ if ( ! config ) {
2621+ // The inactive branch never captures a config, so there is nothing to restore: writing
2622+ // the `previousContextValues` snapshot would re-mirror a list that predates any profile
2623+ // created while the purge held the lock.
2624+ return false
2625+ }
26342626
2635- if ( signal . aborted ) return false
2627+ // Keep `id`: the manager reuses it for the mode associations that point at this profile.
2628+ const { name : _name , ...providerSettings } = config
2629+ await this . providerSettingsManager . saveConfig ( name , providerSettings )
26362630
2637- await this . providerSettingsManager . activateProfile ( { name } )
2631+ if ( signal . aborted ) return false
26382632
2639- if ( signal . aborted ) return false
2633+ await this . providerSettingsManager . activateProfile ( { name } )
26402634
2641- // Restore the whole provider slice, not just the name pointers: a partial activation may
2642- // have left the replacement's provider settings under the deleted name. `id` is manager
2643- // metadata, not a setting, so it must not reach `ContextProxy`. `apiProvider` belongs to
2644- // this slice, so the snapshot below deliberately does not carry it: writing it again
2645- // would overwrite the restored provider with the pre-delete pointer. It is still passed
2646- // as the ownership discriminator, because the profile being restored is the one whose
2647- // own secrets must survive this write.
2648- const { id : _id , ...settings } = providerSettings
2649- await this . contextProxy . setProviderSettings ( settings , config . apiProvider )
2635+ if ( signal . aborted ) return false
26502636
2651- if ( signal . aborted ) return false
2637+ // Restore the whole provider slice, not just the name pointers: a partial activation may
2638+ // have left the replacement's provider settings under the deleted name. `id` is manager
2639+ // metadata, not a setting, so it must not reach `ContextProxy`. `apiProvider` belongs to
2640+ // this slice, so the snapshot below deliberately does not carry it: writing it again
2641+ // would overwrite the restored provider with the pre-delete pointer. It is still passed
2642+ // as the ownership discriminator, because the profile being restored is the one whose
2643+ // own secrets must survive this write.
2644+ const { id : _id , ...settings } = providerSettings
2645+ await this . contextProxy . setProviderSettings ( settings , config . apiProvider )
26522646
2653- // `saveConfig` above reused the profile's `id`, so the mode bindings the purge dropped
2654- // name a profile that exists again. Re-apply them in the manager with a per-write abort
2655- // check, then mirror the pre-delete map into ContextProxy: the snapshot below restores
2656- // every mode, including the ones the purge never touched.
2657- for ( const [ mode , configId ] of Object . entries ( removedModeConfigs ) ) {
2658- if ( signal . aborted ) return false
2659- await this . providerSettingsManager . setModeConfig ( mode as Mode , configId )
2660- }
2647+ if ( signal . aborted ) return false
26612648
2649+ // `saveConfig` above reused the profile's `id`, so the mode bindings the purge dropped
2650+ // name a profile that exists again. Re-apply them in the manager with a per-write abort
2651+ // check, then mirror the pre-delete map into ContextProxy: the snapshot below restores
2652+ // every mode, including the ones the purge never touched.
2653+ for ( const [ mode , configId ] of Object . entries ( removedModeConfigs ) ) {
26622654 if ( signal . aborted ) return false
2663-
2664- await this . contextProxy . setValues ( previousContextValues )
2665- return true
2655+ await this . providerSettingsManager . setModeConfig ( mode as Mode , configId )
26662656 }
26672657
2668- // The inactive branch never captures a config, so there is nothing to restore: writing the
2669- // `previousContextValues` snapshot would re-mirror a list that predates any profile created
2670- // while the purge held the lock.
2671- return false
2658+ if ( signal . aborted ) return false
2659+
2660+ await this . contextProxy . setValues ( previousContextValues )
2661+ return true
26722662 }
26732663
26742664 /**
0 commit comments