Skip to content

Commit 5d17f56

Browse files
feat: add lock toggle to pin API config across all modes in workspace (#11295)
* feat: add lock toggle to pin API config across all modes in workspace Add a lock/unlock toggle inside the API config selector popover (next to the settings gear) that, when enabled, applies the selected API configuration to all modes in the current workspace. - Add lockApiConfigAcrossModes to ExtensionState and WebviewMessage types - Store setting in workspaceState (per-workspace, not global) - When locked, activateProviderProfile sets config for all modes - Lock icon in ApiConfigSelector popover bottom bar next to gear - Full i18n: English + 17 locale translations (all mention workspace scope) - 9 new tests: 2 ClineProvider, 2 handler, 5 UI (77 total pass) * refactor: replace write-fan-out with read-time override for lock API config The original lock implementation used setModeConfig() fan-out to write the locked config to ALL modes globally. Since the lock flag lives in workspace- scoped workspaceState but modeApiConfigs are in global secrets, this caused cross-workspace data destruction. Replaced with read-time guards: - handleModeSwitch: early return when lock is on (skip per-mode config load) - createTaskWithHistoryItem: skip mode-based config restoration under lock - activateProviderProfile: removed fan-out block - lockApiConfigAcrossModes handler: simplified to flag + state post only - Fixed pre-existing workspaceState mock gap in ClineProvider.spec.ts and ClineProvider.sticky-profile.spec.ts
1 parent 6826e20 commit 5d17f56

34 files changed

Lines changed: 732 additions & 4 deletions

packages/types/src/vscode-extension-host.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ export type ExtensionState = Pick<
336336
| "showWorktreesInHomeScreen"
337337
| "disabledTools"
338338
> & {
339+
lockApiConfigAcrossModes?: boolean
339340
version: string
340341
clineMessages: ClineMessage[]
341342
currentTaskItem?: HistoryItem
@@ -524,6 +525,7 @@ export interface WebviewMessage {
524525
| "searchFiles"
525526
| "toggleApiConfigPin"
526527
| "hasOpenedModeSelector"
528+
| "lockApiConfigAcrossModes"
527529
| "clearCloudAuthSkipModel"
528530
| "cloudButtonClicked"
529531
| "rooCloudSignIn"

pnpm-lock.yaml

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core/webview/ClineProvider.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,8 @@ export class ClineProvider
899899
// Load the saved API config for the restored mode if it exists.
900900
// Skip mode-based profile activation if historyItem.apiConfigName exists,
901901
// since the task's specific provider profile will override it anyway.
902-
if (!historyItem.apiConfigName) {
902+
const lockApiConfigAcrossModes = this.context.workspaceState.get("lockApiConfigAcrossModes", false)
903+
if (!historyItem.apiConfigName && !lockApiConfigAcrossModes) {
903904
const savedConfigId = await this.providerSettingsManager.getModeConfigId(historyItem.mode)
904905
const listApiConfig = await this.providerSettingsManager.listConfig()
905906

@@ -1316,6 +1317,13 @@ export class ClineProvider
13161317

13171318
this.emit(RooCodeEventName.ModeChanged, newMode)
13181319

1320+
// If workspace lock is on, keep the current API config — don't load mode-specific config
1321+
const lockApiConfigAcrossModes = this.context.workspaceState.get("lockApiConfigAcrossModes", false)
1322+
if (lockApiConfigAcrossModes) {
1323+
await this.postStateToWebview()
1324+
return
1325+
}
1326+
13191327
// Load the saved API config for the new mode if it exists.
13201328
const savedConfigId = await this.providerSettingsManager.getModeConfigId(newMode)
13211329
const listApiConfig = await this.providerSettingsManager.listConfig()
@@ -2081,6 +2089,7 @@ export class ClineProvider
20812089
openRouterImageGenerationSelectedModel,
20822090
featureRoomoteControlEnabled,
20832091
isBrowserSessionActive,
2092+
lockApiConfigAcrossModes,
20842093
} = await this.getState()
20852094

20862095
let cloudOrganizations: CloudOrganizationMembership[] = []
@@ -2229,6 +2238,7 @@ export class ClineProvider
22292238
profileThresholds: profileThresholds ?? {},
22302239
cloudApiUrl: getRooCodeApiUrl(),
22312240
hasOpenedModeSelector: this.getGlobalState("hasOpenedModeSelector") ?? false,
2241+
lockApiConfigAcrossModes: lockApiConfigAcrossModes ?? false,
22322242
alwaysAllowFollowupQuestions: alwaysAllowFollowupQuestions ?? false,
22332243
followupAutoApproveTimeoutMs: followupAutoApproveTimeoutMs ?? 60000,
22342244
includeDiagnosticMessages: includeDiagnosticMessages ?? true,
@@ -2464,6 +2474,7 @@ export class ClineProvider
24642474
stateValues.codebaseIndexConfig?.codebaseIndexOpenRouterSpecificProvider,
24652475
},
24662476
profileThresholds: stateValues.profileThresholds ?? {},
2477+
lockApiConfigAcrossModes: this.context.workspaceState.get("lockApiConfigAcrossModes", false),
24672478
includeDiagnosticMessages: stateValues.includeDiagnosticMessages ?? true,
24682479
maxDiagnosticMessages: stateValues.maxDiagnosticMessages ?? 50,
24692480
includeTaskHistoryInEnhance: stateValues.includeTaskHistoryInEnhance ?? true,

src/core/webview/__tests__/ClineProvider.apiHandlerRebuild.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,11 @@ describe("ClineProvider - API Handler Rebuild Guard", () => {
171171
store: vi.fn().mockImplementation((key: string, value: string | undefined) => (secrets[key] = value)),
172172
delete: vi.fn().mockImplementation((key: string) => delete secrets[key]),
173173
},
174+
workspaceState: {
175+
get: vi.fn().mockReturnValue(undefined),
176+
update: vi.fn().mockResolvedValue(undefined),
177+
keys: vi.fn().mockReturnValue([]),
178+
},
174179
subscriptions: [],
175180
extension: {
176181
packageJSON: { version: "1.0.0" },

0 commit comments

Comments
 (0)